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
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)
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
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()
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 _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)
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])
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)
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
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_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
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 ')