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