Exemple #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
Exemple #3
0
def add_apploader_date(header: bytes, metadata: Metadata):
	try:
		apploader_date = header[0x2440:0x2450].decode('ascii').rstrip('\0')
		try:
			actual_date = datetime.strptime(apploader_date, '%Y/%m/%d')
			year = actual_date.year
			month = actual_date.month
			day = actual_date.day
			metadata.specific_info['Build Date'] = Date(year, month, day)
			if not metadata.release_date or metadata.release_date.is_guessed:
				metadata.release_date = Date(year, month, day, True)
		except ValueError:
			pass
	except UnicodeDecodeError:
		pass
Exemple #4
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)
Exemple #5
0
def parse_woz_kv(rompath: str, metadata: Metadata, key: str, value: str):
	#rompath is just here for making warnings look better which is a bit silly I think… hm
	if key in {'side', 'side_name', 'contributor', 'image_date', 'collection', 'requires_platform'}:
		#No use for these
		#"collection" is not part of the spec but it shows up and it just says where the image came from
		#requires_platform is not either, it just seems to be "apple2" so far and I don't get it
		return

	if key == 'version':
		#Note that this is free text
		if not value.startswith('v'):
			value = 'v' + value
		metadata.specific_info['Version'] = value
	elif key == 'title':
		metadata.add_alternate_name(value, 'Header Title')
	elif key == 'subtitle':
		metadata.specific_info['Subtitle'] = value
	elif key == 'requires_machine':
		if metadata.specific_info.get('Machine'):
			#Trust the info from the INFO chunk more if it exists
			return
		machines = set()
		for machine in value.split('|'):
			if machine in woz_meta_machines:
				machines.add(woz_meta_machines[machine])
			else:
				print('Unknown compatible machine in Woz META chunk', rompath, machine)
		metadata.specific_info['Machine'] = machines
	elif key == 'requires_ram':
		#Should be in INFO chunk, but sometimes isn't
		if value[-1].lower() == 'k':
			value = value[:-1]
		try:
			metadata.specific_info['Minimum RAM'] = int(value)
		except ValueError:
			pass
	elif key == 'publisher':
		metadata.publisher = consistentify_manufacturer(value)
	elif key == 'developer':
		metadata.developer = consistentify_manufacturer(value)
	elif key == 'copyright':
		metadata.specific_info['Copyright'] = value
		try:
			metadata.release_date = Date(value)
		except ValueError:
			pass
	elif key == 'language':
		metadata.languages = {lang for lang in (get_language_by_english_name(lang_name) for lang_name in value.split('|')) if lang}
	elif key == 'genre':
		#This isn't part of the specification, but I've seen it
		if value == 'rpg':
			metadata.genre = 'RPG'
		else:
			metadata.genre = value.capitalize() if value.islower() else value
	elif key == 'notes':
		#This isn't part of the specification, but I've seen it
		metadata.add_notes(value)
	else:
		if main_config.debug:
			print('Unknown Woz META key', rompath, key, value)
def parse_sdsc_header(rom: 'FileROM', metadata: 'Metadata', header: bytes):
	major_version = decode_bcd(header[0])
	minor_version = decode_bcd(header[1])
	metadata.specific_info['Version'] = 'v{0}.{1}'.format(major_version, minor_version)

	day = decode_bcd(header[2])
	month = decode_bcd(header[3])
	year = _decode_bcd_multi(header[4:6])
	metadata.release_date = Date(year, month, day)

	author_offset = int.from_bytes(header[6:8], 'little')
	name_offset = int.from_bytes(header[8:10], 'little')
	description_offset = int.from_bytes(header[10:12], 'little')
	if 0 < author_offset < 0xffff:
		#Assume sane maximum of 255 chars
		try:
			metadata.developer = metadata.publisher = rom.read(seek_to=author_offset, amount=255).partition(b'\x00')[0].decode('ascii')
		except UnicodeDecodeError:
			pass
	if 0 < name_offset < 0xffff:
		try:
			metadata.add_alternate_name(rom.read(seek_to=name_offset, amount=255).partition(b'\x00')[0].decode('ascii'), 'Header Title')
		except UnicodeDecodeError:
			pass
	if 0 < description_offset < 0xffff:
		try:
			metadata.descriptions['Description'] = rom.read(seek_to=description_offset, amount=255).partition(b'\x00')[0].decode('ascii')
		except UnicodeDecodeError:
			pass
Exemple #7
0
    def _add_metadata_fields(self) -> None:
        self._has_inited_metadata = True
        self.metadata.specific_info['Source File'] = self.machine.source_file
        self.metadata.specific_info['Family'] = self.machine.family
        if self.machine.has_parent:
            self.metadata.specific_info['Has Parent?'] = True

        self.metadata.release_date = Date(self.machine.xml.findtext('year'))

        self.metadata.specific_info[
            'Number of Players'] = self.machine.number_of_players
        if self.machine.is_mechanical:
            self.metadata.specific_info['Is Mechanical?'] = True
        if self.machine.uses_device('ticket_dispenser'):
            self.metadata.specific_info['Dispenses Tickets?'] = True
        self.metadata.specific_info['Coin Slots'] = self.machine.coin_slots
        if self.machine.requires_chds:
            self.metadata.specific_info['Requires CHD?'] = True
        if self.machine.romless:
            self.metadata.specific_info['Romless'] = True
        self.metadata.specific_info['Slot Names'] = {
            next(iter(slot.instances))[0]
            for slot in self.machine.media_slots if slot.instances
        }  #I guess I only expect one?
        self.metadata.specific_info[
            'Software Lists'] = self.machine.software_list_names
        self.metadata.series = self.machine.series
        bios = self.machine.bios
        if bios:
            self.metadata.specific_info['BIOS Used'] = bios
        if self.machine.samples_used:
            self.metadata.specific_info[
                'Samples Used'] = self.machine.samples_used
        arcade_system = self.machine.arcade_system
        if arcade_system:
            self.metadata.specific_info['Arcade System'] = arcade_system

        licensed_from = self.machine.licensed_from
        if self.machine.licensed_from:
            self.metadata.specific_info['Licensed From'] = licensed_from

        hacked_by = self.machine.hacked_by
        if self.machine.hacked_by:
            self.metadata.specific_info['Hacked By'] = hacked_by

        self.metadata.developer, self.metadata.publisher = self.machine.developer_and_publisher

        self.metadata.specific_info[
            'BestGames Rating'] = self.machine.bestgames_opinion
        self.metadata.specific_info[
            'Version Added'] = self.machine.version_added

        if self.machine.requires_artwork:
            self.metadata.specific_info['Requires Artwork?'] = True
        if self.machine.unofficial:
            self.metadata.specific_info['Is Unofficial?'] = True
        if self.machine.no_sound_hardware:
            self.metadata.specific_info['Has No Sound Hardware?'] = True
        if self.machine.incomplete:
            self.metadata.specific_info['Is Incomplete?'] = True
Exemple #8
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)
Exemple #9
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
Exemple #10
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()
Exemple #11
0
def _add_info_from_copyright_string(metadata: Metadata, copyright_string: str):
    metadata.specific_info['Copyright'] = copyright_string
    copyright_match = _copyright_regex.match(copyright_string)
    if copyright_match:
        maker = copyright_match[1].strip().rstrip(',')
        maker = _t_with_zero.sub('T-', maker)
        maker = _t_not_followed_by_dash.sub('T-', maker)
        if maker in _licensee_codes:
            metadata.publisher = _licensee_codes[maker]
        year = copyright_match[2]
        month: Union[str, int]
        try:
            month = datetime.strptime(copyright_match[3], '%b').month
        except ValueError:
            #There are other spellings such as JUR, JLY out there, but oh well
            month = '??'
        metadata.specific_info['Copyright Date'] = Date(year, month)
        if not metadata.release_date:
            metadata.release_date = Date(year, month, is_guessed=True)
def _parse_release_date(release_info: str) -> Optional[Date]:
	if _is_release_date_with_thing_at_end.match(release_info):
		release_info = release_info[:8]

	if len(release_info) != 8:
		return None

	year = release_info[0:4]
	month = release_info[4:6]
	day = release_info[6:8]
	
	return Date(year=None if year == 'xxxx' else year, month=None if month == 'xx' else month, day=None if day == 'xx' else day, is_guessed='x' in release_info or '?' in release_info)
Exemple #13
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
def get_date_from_filename_tags(tags: Sequence[str]) -> Optional[Date]:
	for tag in tags:
		if (date_match := _date_regex.match(tag)):
			groupdict = date_match.groupdict()
			#I _hate_ this. There's no way I can find to make this code not suck titty balls
			_year = groupdict.get('year')
			_year2 = groupdict.get('year2')
			_year3 = groupdict.get('year3')
			year = _year if _year else (_year2 if _year2 else _year3)
			_month = groupdict.get('month')
			_month2 = groupdict.get('month2')

			month_match = _month if _month else _month2
			_day = groupdict.get('day') 
			_day2 = groupdict.get('day2') 
			day = _day if _day else _day2
			return Date(year, month_match, day)
	def add_metadata(self) -> None:
		self.metadata.platform = self.platform_config.name #TODO Not necessarily a thing
		self.metadata.media_type = MediaType.Executable
		if 'developer' in self.info:
			self.metadata.developer = self.info['developer']
		if 'publisher' in self.info:
			self.metadata.publisher = self.info['publisher']
		if 'year' in self.info:
			self.metadata.release_date = Date(self.info['year'])
		if 'category' in self.info:
			self.metadata.categories = [self.info['category']]
		if 'genre' in self.info:
			self.metadata.genre = self.info['genre']
		if 'subgenre' in self.info:
			self.metadata.subgenre = self.info['subgenre']
		if 'notes' in self.info:
			self.metadata.add_notes(self.info['notes'])
		self.additional_metadata()
Exemple #16
0
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…
Exemple #17
0
def add_info_from_uze_header(header: bytes, metadata: Metadata):
    #Header version: 6
    #Target: 7 (0 = ATmega644, 1 = reserved for ATmega1284)
    #Program size: 8-0xc (LE)
    metadata.release_date = Date(int.from_bytes(header[0xc:0xe], 'little'))
    metadata.add_alternate_name(
        header[0xe:0x2e].decode('ascii',
                                errors='backslashreplace').rstrip('\0'),
        'Banner Title')
    metadata.developer = metadata.publisher = header[0x2e:0x4e].decode(
        'ascii', errors='backslashreplace').rstrip('\0')
    #Icon (sadly unused) (16 x 16, BBGGGRRR): 0x4e:0x14e
    #CRC32: 0x14e:0x152
    uses_mouse = header[0x152] == 1
    metadata.specific_info['Uses Mouse?'] = uses_mouse
    #Potentially it could use other weird SNES peripherals but this should do
    metadata.input_info.add_option(
        snes_controllers.mouse if uses_mouse else snes_controllers.controller)

    description = header[0x153:0x193].decode(
        'ascii', errors='backslashreplace').rstrip('\0')
    if description:
        #Official documentation claims this is unused, but it seems that it is used after all (although often identical to title)
        metadata.descriptions['Banner Description'] = description
Exemple #18
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
Exemple #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])
Exemple #20
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
Exemple #21
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']
	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)
Exemple #23
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)
Exemple #24
0
def _add_info_from_tdb_entry(tdb: TDB, db_entry: ElementTree.Element,
                             metadata: Metadata):
    metadata.add_alternate_name(db_entry.attrib['name'], 'GameTDB Name')
    #(Pylint is on drugs if I don't add more text here) id: What we just found
    #(it thinks I need an indented block) type: 3DS, 3DSWare, VC, etc (we probably don't need to worry about that)
    #region: PAL, etc (we can see region code already)
    #languages: "EN" "JA" etc (I guess we could parse this if the filename isn't good enough for us)
    #rom: What they think the ROM should be named
    #case: Has "color" and "versions" attribute? I don't know what versions does but I presume it all has to do with the db_entry box

    if main_config.debug:
        for element in db_entry:
            if element.tag not in ('developer', 'publisher', 'date', 'rating',
                                   'id', 'type', 'region', 'languages',
                                   'locale', 'genre', 'wi-fi', 'input', 'rom',
                                   'case', 'save'):
                print('uwu', db_entry.attrib['name'], 'has unknown', element,
                      'tag')

    developer = db_entry.findtext('developer')
    if developer and developer != 'N/A':
        metadata.developer = _clean_up_company_name(developer)
    publisher = db_entry.findtext('publisher')
    if publisher:
        metadata.publisher = _clean_up_company_name(publisher)
    date = db_entry.find('date')
    if date is not None:
        year = date.attrib.get('year')
        month = date.attrib.get('month')
        day = date.attrib.get('day')
        if any([year, month, day]):
            metadata.release_date = Date(year, month, day)

    genre = db_entry.findtext('genre')
    if genre:
        tdb.parse_genre(metadata, genre)

    for locale in db_entry.iterfind('locale'):
        synopsis = locale.findtext('synopsis')
        if synopsis:
            key_name = f"Synopsis-{locale.attrib.get('lang')}" if 'lang' in locale.attrib else 'Synopsis'
            metadata.descriptions[key_name] = synopsis

    rating = db_entry.find('rating')
    if rating is not None:
        #Rating board (attrib "type") is implied by region (db_entrys released in e.g. both Europe and Australia just tend to not have this here)
        value = rating.attrib.get('value')
        if value:
            metadata.specific_info['Age Rating'] = value

        descriptors = {e.text for e in rating.iterfind('descriptor')}
        if descriptors:
            metadata.specific_info['Content Warnings'] = descriptors

    #This stuff will depend on platform…

    save = db_entry.find('save')
    if save is not None:
        blocks = save.attrib.get('blocks')
        #Other platforms may have "size" instead, also there are "copy" and "move" attributes which we'll ignore
        if blocks:
            if metadata.platform == 'Wii':
                metadata.save_type = SaveType.Internal
            elif metadata.platform == 'GameCube':
                metadata.save_type = SaveType.MemoryCard
        #Have not seen a db_entry with blocks = 0 or missing blocks or size

    if metadata.platform != 'GameCube':
        wifi = db_entry.find('wi-fi')
        if wifi:
            features = {feature.text for feature in wifi.iterfind('feature')}
            metadata.specific_info['Wifi Features'] = features
            #online, download, score, nintendods

    input_element = db_entry.find('input')
    if input_element is not None:
        #TODO: DS has players-multi-cart and players-single-cart instead (which one do I want?)
        number_of_players = input_element.attrib.get('players', None)
        if number_of_players is not None:  #Maybe 0 could be a valid amount? For like demos or something
            metadata.specific_info['Number of Players'] = number_of_players

        if metadata.platform != 'GameCube':
            #wiimote, nunchuk, motionplus, db_entrycube, nintendods, classiccontroller, wheel, zapper, balanceboard, wiispeak, microphone, guitar, drums, dancepad, keyboard, draw
            optional_controls = set()
            required_controls = set()
            for control in input_element.iterfind('control'):
                control_type = control.attrib.get('type')
                if control.attrib.get('required', 'false') == 'true':
                    required_controls.add(control_type)
                else:
                    optional_controls.add(control_type)

                #cbf setting up input_info just yet
                metadata.specific_info[
                    'Optional Additional Controls'] = optional_controls
                metadata.specific_info[
                    'Required Additional Controls'] = required_controls
Exemple #25
0
def add_wii_homebrew_metadata(rom: FolderROM, metadata: 'Metadata'):
	metadata.specific_info['Executable Name'] = rom.relevant_files['boot.dol'].name

	icon_path = rom.get_file('icon.png', True)
	if icon_path:
		metadata.images['Banner'] = str(icon_path)
		#Unfortunately the aspect ratio means it's not really great as an icon

	xml_path = rom.relevant_files['meta.xml']
	if xml_path.is_file():
		try:
			meta_xml = ElementTree.parse(str(xml_path))
			name = meta_xml.findtext('name')
			if name:
				metadata.add_alternate_name(name, 'Banner Title')
				rom.ignore_name = True

			coder = meta_xml.findtext('coder')
			if not coder:
				coder = meta_xml.findtext('author')
			metadata.developer = metadata.publisher = coder

			version = meta_xml.findtext('version')
			if version:
				version = version.removeprefix('rev').removeprefix('r').lstrip()
				
				if version[0] != 'v':
					version = 'v' + version
				metadata.specific_info['Version'] = version

			release_date = meta_xml.findtext('release_date')
			if release_date:
				#Not interested in hour/minute/second/etc
				release_date = release_date[0:8]
				actual_date = None
				date_formats = [
					'%Y%m%d', #The one actually specified by the meta.xml format
					'%Y%m%d%H%M%S',
					'%d/%m/%Y', #Hmm this might be risky because of potential ambiguity with American dates
					'%Y-%m-%d',
				]
				for date_format in date_formats:
					try:
						actual_date = datetime.strptime(release_date, date_format)
						break
					except ValueError:
						continue
				if actual_date:
					year = actual_date.year
					month = actual_date.month
					day = actual_date.day
					metadata.release_date = Date(year, month, day)

			short_description = meta_xml.findtext('short_description')
			if short_description:
				metadata.descriptions['Description'] = short_description
			long_description = meta_xml.findtext('long_description')
			if long_description:
				metadata.descriptions['Long Description'] = long_description

		except ElementTree.ParseError as etree_error:
			if main_config.debug:
				print('Ah bugger this Wii homebrew XML has problems', rom.path, etree_error)
Exemple #26
0
    def _add_metadata_from_receipt(self) -> None:
        if not self.receipt:
            return

        game = self.receipt['game']
        upload = self.receipt['upload']
        #build, files, installerName probably not needed

        title = game.get('title')
        if title:
            self._name = fix_name(title)
        self.metadata.specific_info['Game ID'] = game.get('id')
        self.metadata.documents['Homepage'] = game.get('url')

        description = game.get('shortText')
        if description:
            self.metadata.descriptions['Description'] = description

        self.game_type = game.get(
            'type', 'default')  #Default, flash, unity, java, html
        self.category = game.get(
            'classification', 'game'
        )  #game, tool, assets, game_mod, physical_game, soundtrack, other, comic, book
        created_at = game.get('createdAt')
        published_at = game.get('publishedAt')
        if created_at:
            creation_date = datetime.date.fromisoformat(created_at[:10])
            self.metadata.specific_info['Creation Date'] = Date(
                creation_date.year, creation_date.month, creation_date.day)
        if published_at:
            release_date = datetime.date.fromisoformat(published_at[:10])
            self.metadata.release_date = Date(release_date.year,
                                              release_date.month,
                                              release_date.day)

        #coverUrl, stillCoverUrl might be useful? I dunno
        #platforms is what platforms the game _can_ be available for, but it doesn't tell us about this exe
        #minPrice, canBeBought, sale aren't so useful here as this receipt is generated at the time this game is downloaded I think which might be out of date
        user = game.get('user')
        if user:
            user_name = user.get('displayName')
            #not using the second param of .get here because we also don't want it to be an empty string
            if not user_name:
                user_name = user.get('username')
            if user_name:
                self.metadata.developer = self.metadata.publisher = user_name
            #developer and pressUser here just indicate if this user (who has uploaded the game) has ticked a box saying they are a developer or press, which doesn't seem to matter
            self.metadata.documents['Developer Homepage'] = user.get('url')

        if upload:
            build_name = upload.get('displayName')
            if not build_name:
                build_name = upload.get('filename')
            self.metadata.specific_info['Build Name'] = build_name
            self.is_demo = upload.get('demo')
            if self.is_demo and not 'demo' in self.name.lower():
                self._name += ' (Demo)'
            self.metadata.specific_info['Upload Type'] = upload.get(
                'type', 'default'
            )  #default, flash, unity, java, html, soundtrack, book, video, documentation, mod, audio_assets, graphical_assets, sourcecode, other
            self.platforms = tuple(upload.get(
                'platforms',
                {}).keys())  #I think the values show if it's x86/x64 but eh
            #Not sure what channelName or preorder does
            upload_created_at = upload.get('createdAt')
            upload_updated_at = upload.get('updatedAt')
            if upload_created_at:
                upload_creation_date = datetime.date.fromisoformat(
                    upload_created_at[:10])
                self.metadata.specific_info['Upload Creation Date'] = Date(
                    upload_creation_date.year, upload_creation_date.month,
                    upload_creation_date.day)
            if upload_updated_at:
                upload_date = datetime.date.fromisoformat(
                    upload_updated_at[:10])
                self.metadata.specific_info['Upload Date'] = Date(
                    upload_date.year, upload_date.month, upload_date.day)
Exemple #27
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 ')
Exemple #28
0
def add_metadata_from_appinfo_common_section(game: 'SteamGame', common: Mapping[bytes, Any]):
	if 'Icon' not in game.metadata.images:
		add_icon_from_common_section(game, common)

	#eulas is a list, so it could be used to detect if game has third-party EULA
	#small_capsule and header_image refer to image files that don't seem to be there so I dunno
	#workshop_visible and community_hub_visible could also tell you stuff about if the game has a workshop and a... community hub
	#releasestate: 'released' might be to do with early access?
	#exfgls = exclude from game library sharing
	#b'requireskbmouse' and b'kbmousegame' are also things, but don't seem to be 1:1 with games that have controllersupport = none

	oslist = common.get(b'oslist')
	if not main_config.use_steam_as_platform:
		#It's comma separated, but we can assume platform if there's only one (and sometimes config section doesn't do the thing)
		if oslist == b'windows':
			game.metadata.platform = 'Windows'
		if oslist == b'macos':
			game.metadata.platform = 'Mac'
		if oslist == b'linux':
			game.metadata.platform = 'Linux'
		
	#osarch is something like b'32' or b'64', osextended is sometimes 'macos64' etc

	app_retired_publisher_request = common.get(b'app_retired_publisher_request')
	if app_retired_publisher_request:
		game.metadata.specific_info['No Longer Purchasable'] = app_retired_publisher_request.data == 1
	#You can't know if a game's delisted entirely unless you go to the store API to find if that returns success or not, because the appinfo stuff is a cache and holds on to data that no longer exists

	language_list = common.get(b'languages')
	if language_list:
		game.metadata.languages = translate_language_list(language_list)
	else:
		supported_languages = common.get(b'supported_languages')
		if supported_languages:
			#Hmm… this one goes into more detail actually, you have not just "supported" but "full_audio" and "subtitles"
			#But for now let's just look at what else exists
			game.metadata.languages = translate_language_list(supported_languages)

	add_genre(game, common)

	steam_release_timestamp = common.get(b'steam_release_date')
	#Seems that original_release_date is here sometimes, and original_release_date sometimes appears along with steam_release_date where a game was only put on Steam later than when it was actually released elsewhere
	#Sometimes these are equal, or off by like one day (which is possibly timezone related)
	original_release_timestamp = common.get(b'original_release_date')

	release_date = original_release_timestamp
	if not release_date:
		release_date = steam_release_timestamp
	#Maybe I should put in an option to prefer Steam release date
		
	if release_date:
		release_datetime = datetime.datetime.fromtimestamp(release_date.data)
		game.metadata.release_date = Date(release_datetime.year, release_datetime.month, release_datetime.day)
	if original_release_timestamp and steam_release_timestamp:
		steam_release_datetime = datetime.datetime.fromtimestamp(steam_release_timestamp.data)
		game.metadata.specific_info['Steam Release Date'] = Date(steam_release_datetime.year, steam_release_datetime.month, steam_release_datetime.day)

	store_asset_mtime = common.get(b'store_asset_mtime')
	if store_asset_mtime:
		store_asset_timestamp = datetime.datetime.fromtimestamp(store_asset_mtime.data)
		game.metadata.specific_info['Store Asset Modification Time'] = Date(store_asset_timestamp.year, store_asset_timestamp.month, store_asset_timestamp.day)

	store_categories_list = common.get(b'category')
	if store_categories_list:
		#keys are category_X where X is some arbitrary ID, values are always Integer = 1
		#This is the thing where you go to the store sidebar and it's like "Single-player" "Multi-player" "Steam Achievements" etc"
		cats = {store_categories.get(key, key) for key in (key.decode('utf-8', errors='backslashreplace') for key in store_categories_list.keys())}
		game.metadata.specific_info['Store Categories'] = cats #meow
		game.metadata.specific_info['Has Achievements?'] = 'Steam Achievements' in cats
		game.metadata.specific_info['Has Trading Cards?'] = 'Steam Trading Cards' in cats
		is_single_player_only = True
		for cat in cats:
			if 'multiplayer' in cat.lower() or 'multi-player' in cat.lower() or 'co-op' in cat.lower() or 'split screen' in cat.lower():
				is_single_player_only = False
				break
		if is_single_player_only:
			game.metadata.specific_info['Number of Players'] = 1
		
	has_adult_content = common.get(b'has_adult_content') #Integer object with data = 0 or 1, as most bools here seem to be
	if has_adult_content:
		game.metadata.specific_info['Has Adult Content?'] = bool(has_adult_content.data)
	has_violence = common.get(b'has_adult_content_violence')
	if has_violence:
		game.metadata.specific_info['Has Violent Content?'] = bool(has_violence.data)
	has_sex = common.get(b'has_adult_content_sex') #uwu
	if has_sex:
		game.metadata.specific_info['Has Sexual Content?'] = bool(has_sex.data)
	
	only_vr = common.get(b'onlyvrsupport')
	vr_support = common.get(b'openvrsupport')
	if only_vr is not None and only_vr.data:
		game.metadata.specific_info['VR Support'] = 'Required'
	elif vr_support:
		#b'1'
		game.metadata.specific_info['VR Support'] = 'Optional'

	metacritic_score = common.get(b'metacritic_score')
	if metacritic_score:
		#Well why not
		game.metadata.specific_info['Metacritic Score'] = metacritic_score.data
	metacritic_url = common.get(b'metacritic_fullurl')
	if metacritic_url:
		game.metadata.documents['Metacritic Page'] = metacritic_url.decode('utf8', errors='ignore')
	metacritic_name = common.get(b'metacritic_name')
	if metacritic_name:
		game.metadata.add_alternate_name(metacritic_name.decode('utf8', errors='ignore'), 'Metacritic Name')

	review_score = common.get(b'review_score')
	#This is Steam's own review section, I guess?
	#This seems to be a number from 2 to 9 inclusive. Not sure what it means though
	#There is also review_score_bombs? What the heck
	if review_score:
		game.metadata.specific_info['Review Score'] = review_score.data
	review_percentage = common.get(b'review_percentage')
	#Also seemingly related to Steam reviews, and there is also a review_percentage_bombs, but I still don't know exactly what this does
	if review_percentage:
		game.metadata.specific_info['Review Percentage'] = review_percentage.data
	
	sortas = common.get(b'sortas')
	if sortas:
		game.metadata.specific_info['Sort Name'] = sortas.decode('utf8', errors='backslashreplace')

	game.metadata.specific_info['Controlller Support'] = common.get(b'controller_support', b'none').decode('utf-8', errors='backslashreplace')

	if steam_installation.localization_available:
		store_tag_names = steam_installation.localization['localization']['english']['store_tags']
		store_tag_ids_list = common.get(b'store_tags')
		if store_tag_ids_list:
			store_tags = {store_tag_names.get(id, id) for id in (str(value.data) for value in store_tag_ids_list.values())}
			game.metadata.specific_info['Store Tags'] = store_tags

	franchise_name = None
	associations = common.get(b'associations')

	if associations:
		associations_dict: MutableMapping[str, list[str]] = {}
		for association in associations.values():
			association_type_value = association.get(b'type')
			if isinstance(association_type_value, appinfo.Integer):
				association_type = str(association_type_value.data)
			else:
				association_type = association_type_value.decode('utf-8', errors='ignore')

			association_name_value = association.get(b'name')
			if isinstance(association_name_value, appinfo.Integer):
				association_name = str(association_name_value.data)
			else:
				association_name = association_name_value.decode('utf-8', errors='ignore')
			
			if association_type not in associations_dict:
				associations_dict[association_type] = []
			associations_dict[association_type].append(association_name)

		if 'franchise' in associations_dict:
			franchise_name = associations_dict['franchise'][0]
			franchise_name.removesuffix(' Franchise')
			franchise_name.removesuffix(' Series')
			franchise_name.removeprefix('The ')
			
			franchise_name = normalize_name_case(franchise_name)
			
			not_actual_franchises = ('Playism', 'Hentai', 'Coming-of-Age', 'Wolf RPG Editor', 'Winter Wolves Games', 'Team17 Digital', '&quot;caves rd&quot;', 'Jackbox Games', 'Franchise', 'PopCap')
			if not any(franchise_name.lower() == assoc[0].lower() for assoc_type, assoc in associations_dict.items() if assoc_type != 'franchise') and franchise_name not in not_actual_franchises:
				game.metadata.series = remove_capital_article(franchise_name)

		if 'developer' in associations_dict:
			devs = []
			for dev in associations_dict['developer']:
				dev = normalize_developer(dev)
				if dev.endswith(' (Mac)'):
					game.metadata.specific_info['Mac Developer'] = dev.removesuffix(' (Mac)')
				elif dev.endswith(' (Linux)'):
					game.metadata.specific_info['Linux Developer'] = dev.removesuffix(' (Linux)')
				elif dev not in devs:
					devs.append(dev)

			game.metadata.developer = ', '.join(devs)
		if 'publisher' in associations_dict:
			pubs = []
			for pub in associations_dict['publisher']:
				pub = normalize_developer(pub)
				if pub in {'none', 'Self Published'} and game.metadata.developer:
					pub = game.metadata.developer
				if pub.endswith(' (Mac)'):
					game.metadata.specific_info['Mac Publisher'] = pub.removesuffix(' (Mac)')
				elif pub.endswith(' (Linux)'):
					game.metadata.specific_info['Linux Publisher'] = pub.removesuffix(' (Linux)')
				elif pub not in pubs:
					pubs.append(pub)

			game.metadata.publisher = ', '.join(pubs)