diff --git a/.gitattributes b/.gitattributes index e745ecf73..12d0a6d3d 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2,3 +2,4 @@ *.dat text eol=lf *.xml text eol=lf *.cht text eol=lf +*.py text eol=lf diff --git a/scripts/FBNeo_dat_gen.py b/scripts/FBNeo_dat_gen.py index a8ca123b2..2b4609777 100755 --- a/scripts/FBNeo_dat_gen.py +++ b/scripts/FBNeo_dat_gen.py @@ -1,235 +1,239 @@ -#!/usr/bin/env python3 - -# FBNeo_dat_gen.py -# Written in 2017 by SpiralBrad - -## usage: FBNeo_dat_gen.py [-h] -dat DAT -path PATH [-output_file OUTPUT_FILE] -## [-header_name HEADER_NAME] -## [-header_description HEADER_DESCRIPTION] -## [-header_version HEADER_VERSION] -## -## Generates Final Burn Neo .dat file needed for RetroArch/libretro- -## db/c_converter -## -## optional arguments: -## -h, --help show this help message and exit -## -output_file OUTPUT_FILE -## Path to the target output file; example: FBNeo - -## Arcade games.dat -## -header_name HEADER_NAME -## Override the clrmamepro(name) in the output .dat -## -header_description HEADER_DESCRIPTION -## Override the clrmamepro(description) in the output -## .dat -## -header_version HEADER_VERSION -## Override the clrmamepro(version) in the output .dat -## -## required arguments: -## -dat DAT Misc -> Generate dat file -> Generate dat (Arcade -## only) -## -path PATH Path to a split, ClrMamePro verified and TorrentZipped -## ROM set matching the Arcade only dat -## -## example usage: python3 FBA_dat_gen.py -dat "FBNeo v0.2.97.42 (ClrMame Pro -## XML).dat" -path "/path/to/split/verified/torrentzipped/roms/" -## -output_file "FBNeo - Arcade Games.dat" - -#- To the extent possible under law, the author(s) have dedicated all -#- copyright and related and neighboring rights to this software to the -#- public domain worldwide. This software is distributed without any -#- warranty. -#- -#- You should have received a copy of the CC0 Public Domain Dedication -#- along with this software. If not, see -#- . - -import argparse -import hashlib -import operator -import os -import os.path -import sys -import xml.etree.ElementTree as ET -import zlib - -def main(): - parser = setup_argparse() - args = parser.parse_args() - dat_root = get_datroot(args.dat, parser) - header_name = get_header_name(args) - header_version = get_header_version(args, dat_root, parser) - header_description = get_header_description(args, header_version) - header = generate_dat_header(header_name, header_description, header_version) - game_list = generate_game_list(dat_root, args.path) - output(args, header, game_list) - -def setup_argparse(): - """Set up the argparse arguments and return the argparse instance""" - parser = argparse.ArgumentParser(prog='FBgen.py', description='Generates Final Burn Neo .dat file needed for RetroArch/libretro-db/c_converter') - required_arguments = parser.add_argument_group('required arguments') - required_arguments.add_argument('-dat', help='Misc -> Generate dat file -> Generate dat (Arcade only)', required=True) - required_arguments.add_argument('-path', help='Path to a split, ClrMamePro verified and TorrentZipped ROM set matching the Arcade only dat', required=True) - parser.add_argument('-output_file', help='Path to the target output file; example: FBNeo - Arcade games.dat') - parser.add_argument('-header_name', help='Override the clrmamepro(name) in the output .dat') - parser.add_argument('-header_description', help='Override the clrmamepro(description) in the output .dat') - parser.add_argument('-header_version', help='Override the clrmamepro(version) in the output .dat') - - if len(sys.argv[1:])==0: - parser.print_help() - print('\n'.join(['', - 'example usage: python3 FBA_dat_gen.py -dat "FBNeo v0.2.97.42 (ClrMame Pro ', - ' XML).dat" -path "/path/to/split/verified/torrentzipped/roms/" ', - ' -output_file "FBNeo - Arcade Games.dat"'])) - parser.exit() - return parser - -def get_datroot(dat, parser): - """Get the ElementTree root from the specified file and return the ElementTree root element""" - if not os.path.isfile(dat): - print('File not found: ' + dat) - print('') - parser.print_help() - parser.exit() - else: - dat_tree = ET.parse(dat) - dat_root = dat_tree.getroot() - return dat_root - -def get_header_name(args): - """Return the default header name or the -header_name argument""" - if not args.header_name: - header_name = 'FBNeo - Arcade Games' - else: - header_name = args.header_name - return header_name - -def get_header_version(args, dat_root, parser): - """Return the version from the header version, or the -header_version argument""" - if not args.header_version: - header_version = dat_root.find('header').find('version').text - if not header_version: - print('Version not found in ClrMame Pro XML dat. Please specify with -header_version') - print('') - parser.print_help() - parser.exit() - raise SystemExit() - else: - header_version = args.header_version - return header_version - -def get_header_description(args, version): - """Return the description by generationg via the or the -header_description argument""" - if not args.header_description: - header_description = 'FBNeo v' + version + ' Arcade Games' - else: - header_description = args.header_description - return header_description - -def generate_dat_header(name, description, version): - """Return the textual DAT header from a given , , and """ - header = ['clrmamepro (', - '\t' + 'name "%s"' % name, - '\t' + 'description "%s"' % description, - '\t' + 'version %s' % version, - ')'] - return '\n'.join(header) - -def generate_game_list(dat_root, path): - """Generate the sorted list of games with all metadata and return a textual dat list""" - game_list = [] - - if not os.path.isdir(path): - print('Path not found: ' + path) - print('') - parser.print_help() - parser.exit() - else: - game_entries = [] - for game in dat_root.iter('game'): - # 'gpriders.zip' kludge hack, as of at least 0.2.97.42 - # - rom is exact to 'gprider.zip' and does not exist in a split set - if game.get('name') != 'gpriders': - # same 0.2.97.44 - if game.get('name') != 'natodefa': - if game.get('name') != 'marioo': - entry = GameEntry() - # set name ('description' in FBA-generated-dat) - entry.name = game.find('description').text - # set year - entry.year = game.find('year').text - # set publisher ('manufacturer' in FBA-generated-dat) - entry.publisher = game.find('manufacturer').text - # set zip filename - entry.zip = game.get('name') + '.zip' - zip_path = os.path.join(path, entry.zip) - # set zip size - entry.size = os.path.getsize(zip_path) - # set zip crc - entry.crc = get_crc(zip_path) - # set zip md5 - entry.md5 = get_md5(zip_path) - # set zip sha1 - entry.sha1 = get_sha1(zip_path) - # Add to game_entries list - game_entries.append(entry) - - # Sort game_entries list - game_entries.sort(key=operator.attrgetter('name')) - - # Generate formatted textual list - for entry in game_entries: - text_entry = ['game (', - '\t' + 'name "%s"' % entry.name, - '\t' + 'year "%s"' % entry.year, - '\t' + 'publisher "%s"' % entry.publisher, - '\t' + 'rom ( name %s size %s crc %s md5 %s sha1 %s )' % (entry.zip, entry.size, entry.crc, entry.md5, entry.sha1), - ')', - ''] - game_list.append('\n'.join(text_entry)) - return '\n'.join(game_list) - -def output(args, header, game_list): - """Combine
and and print to stdout, or write to specified -output_file""" - if not args.output_file: - # print to stdout - print('\n'.join([header, '', game_list]).rstrip()) - else: - # write to args.output_file - with open(args.output_file, 'w') as f: - f.write('\n'.join([header, '', game_list])) - -class GameEntry: - name = None - year = None - publisher = None - zip = None - size = None - crc = None - md5 = None - sha1 = None - -def get_crc(file): - """Return the CRC32 hash of """ - prev = 0 - for eachLine in open(file, 'rb'): - prev = zlib.crc32(eachLine, prev) - return str("%X"%(prev & 0xFFFFFFFF)).zfill(8) - -def get_md5(file): - """Return the MD5 hash of """ - m = hashlib.md5() - with open(file, 'rb') as f: - for chunk in iter(lambda: f.read(4096), b""): - m.update(chunk) - return m.hexdigest().zfill(32) - -def get_sha1(file): - """Return the SHA1 hash of """ - m = hashlib.sha1() - with open(file, 'rb') as f: - for chunk in iter(lambda: f.read(4096), b""): - m.update(chunk) - return m.hexdigest().zfill(40) - -if __name__ == '__main__': - main() +#!/usr/bin/env python3 + +# FBNeo_dat_gen.py +# Written in 2017 by SpiralBrad + +## usage: FBNeo_dat_gen.py [-h] --dat DAT --path PATH [--output-file OUTPUT_FILE] +## [--header-name HEADER_NAME] +## [--header-description HEADER_DESCRIPTION] +## [--header-version HEADER_VERSION] +## +## Generates Final Burn Neo .dat file needed for RetroArch/libretro- +## db/c_converter +## +## optional arguments: +## -h, --help show this help message and exit +## -o, --output_file OUTPUT_FILE +## Path to the target output file; example: FBNeo - +## Arcade games.dat +## --header-name HEADER_NAME +## Override the clrmamepro(name) in the output .dat +## --header-description HEADER_DESCRIPTION +## Override the clrmamepro(description) in the output +## .dat +## --header-version HEADER_VERSION +## Override the clrmamepro(version) in the output .dat +## +## required arguments: +## -d, --dat DAT Misc -> Generate dat file -> Generate dat (Arcade +## only) +## -p, --path PATH Path to a split, ClrMamePro verified and TorrentZipped +## ROM set matching the Arcade only dat +## +## example usage: python3 FBA_dat_gen.py --dat "FBNeo v0.2.97.42 (ClrMame Pro +## XML).dat" --path "/path/to/split/verified/torrentzipped/roms/" +## --output-file "FBNeo - Arcade Games.dat" + +#- To the extent possible under law, the author(s) have dedicated all +#- copyright and related and neighboring rights to this software to the +#- public domain worldwide. This software is distributed without any +#- warranty. +#- +#- You should have received a copy of the CC0 Public Domain Dedication +#- along with this software. If not, see +#- . + +import argparse +import hashlib +import operator +import os +import os.path +import sys +import xml.etree.ElementTree as ET +import zlib + +def main(): + parser = setup_argparse() + args = parser.parse_args() + if parser == None: + print("parser is none") + dat_root = get_datroot(args.dat, parser) + header_name = get_header_name(args) + header_version = get_header_version(args, dat_root, parser) + header_description = get_header_description(args, header_version) + header = generate_dat_header(header_name, header_description, header_version) + game_list = generate_game_list(dat_root, args.path, parser) + output(args, header, game_list) + +def setup_argparse(): + """Set up the argparse arguments and return the argparse instance""" + parser = argparse.ArgumentParser(prog='FBgen.py', description='Generates Final Burn Neo .dat file needed for RetroArch/libretro-db/c_converter') + required_arguments = parser.add_argument_group('required arguments') + required_arguments.add_argument('-d', '--dat', help='Misc -> Generate dat file -> Generate dat (Arcade only)', required=True) + required_arguments.add_argument('-p', '--path', help='Path to a split, ClrMamePro verified and TorrentZipped ROM set matching the Arcade only dat', required=True) + parser.add_argument('-o', '--output-file', help='Path to the target output file; example: FBNeo - Arcade games.dat') + parser.add_argument('--header-name', help='Override the clrmamepro(name) in the output .dat') + parser.add_argument('--header-description', help='Override the clrmamepro(description) in the output .dat') + parser.add_argument('--header-version', help='Override the clrmamepro(version) in the output .dat') + + if len(sys.argv) == 1: + parser.print_help() + print('\n'.join(['', + 'example usage: python3 FBA_dat_gen.py --dat "FBNeo v0.2.97.42 (ClrMame Pro ', + ' XML).dat" -path "/path/to/split/verified/torrentzipped/roms/" ', + ' --output-file "FBNeo - Arcade Games.dat"'])) + parser.exit() + return parser + +def get_datroot(dat, parser): + """Get the ElementTree root from the specified file and return the ElementTree root element""" + if not os.path.isfile(dat): + print('File not found: ' + dat) + print('') + parser.print_help() + parser.exit() + else: + dat_tree = ET.parse(dat) + dat_root = dat_tree.getroot() + return dat_root + +def get_header_name(args): + """Return the default header name or the -header_name argument""" + if not args.header_name: + header_name = 'FBNeo - Arcade Games' + else: + header_name = args.header_name + return header_name + +def get_header_version(args, dat_root, parser): + """Return the version from the header version, or the -header_version argument""" + if not args.header_version: + header_version = dat_root.find('header').find('version').text + if not header_version: + print('Version not found in ClrMame Pro XML dat. Please specify with -header_version') + print('') + parser.print_help() + parser.exit() + raise SystemExit() + else: + header_version = args.header_version + return header_version + +def get_header_description(args, version): + """Return the description by generationg via the or the -header_description argument""" + if not args.header_description: + header_description = 'FBNeo v' + version + ' Arcade Games' + else: + header_description = args.header_description + return header_description + +def generate_dat_header(name, description, version): + """Return the textual DAT header from a given , , and """ + header = ['clrmamepro (', + '\t' + 'name "%s"' % name, + '\t' + 'description "%s"' % description, + '\t' + 'version %s' % version, + ')'] + return '\n'.join(header) + +def generate_game_list(dat_root, path, parser): + """Generate the sorted list of games with all metadata and return a textual dat list""" + game_list = [] + + if not os.path.isdir(path): + print(f'Path is not dir or not found: {path}\n') + parser.print_help() + parser.exit() + else: + game_entries = [] + for game in dat_root.iter('game'): + # 'gpriders.zip' kludge hack, as of at least 0.2.97.42 + # - rom is exact to 'gprider.zip' and does not exist in a split set + if game.get('name') != 'gpriders': + # same 0.2.97.44 + if game.get('name') != 'natodefa': + if game.get('name') != 'marioo': + entry = GameEntry() + # set name ('description' in FBA-generated-dat) + entry.name = game.find('description').text + # set year + entry.year = game.find('year').text + # set publisher ('manufacturer' in FBA-generated-dat) + entry.publisher = game.find('manufacturer').text + # set zip filename + entry.zip = game.get('name') + '.zip' + zip_path = os.path.join(path, entry.zip) + if not os.path.exists(zip_path): + print(f'Did not find "{zip_path}", skipping.') + continue + # set zip size + entry.size = os.path.getsize(zip_path) + # set zip crc + entry.crc = get_crc(zip_path) + # set zip md5 + entry.md5 = get_md5(zip_path) + # set zip sha1 + entry.sha1 = get_sha1(zip_path) + # Add to game_entries list + game_entries.append(entry) + + # Sort game_entries list + game_entries.sort(key=operator.attrgetter('name')) + + # Generate formatted textual list + for entry in game_entries: + text_entry = ['game (', + '\t' + 'name "%s"' % entry.name, + '\t' + 'releaseyear "%s"' % entry.year, + '\t' + 'publisher "%s"' % entry.publisher, + '\t' + 'rom ( name %s size %s crc %s md5 %s sha1 %s )' % (entry.zip, entry.size, entry.crc, entry.md5, entry.sha1), + ')', + ''] + game_list.append('\n'.join(text_entry)) + return '\n'.join(game_list) + +def output(args, header, game_list): + """Combine
and and print to stdout, or write to specified --output-file""" + if not args.output_file: + # print to stdout + print('\n'.join([header, '', game_list]).rstrip()) + else: + # write to args.output_file + with open(args.output_file, 'w') as f: + f.write('\n'.join([header, '', game_list])) + +class GameEntry: + name = None + year = None + publisher = None + zip = None + size = None + crc = None + md5 = None + sha1 = None + +def get_crc(file): + """Return the CRC32 hash of """ + prev = 0 + for eachLine in open(file, 'rb'): + prev = zlib.crc32(eachLine, prev) + return str("%X"%(prev & 0xFFFFFFFF)).zfill(8) + +def get_md5(file): + """Return the MD5 hash of """ + m = hashlib.md5() + with open(file, 'rb') as f: + for chunk in iter(lambda: f.read(4096), b""): + m.update(chunk) + return m.hexdigest().zfill(32) + +def get_sha1(file): + """Return the SHA1 hash of """ + m = hashlib.sha1() + with open(file, 'rb') as f: + for chunk in iter(lambda: f.read(4096), b""): + m.update(chunk) + return m.hexdigest().zfill(40) + +if __name__ == '__main__': + main()