oaf-boost/tools/gba-db-builder/gba-db-builder.py
2022-08-04 11:40:24 +08:00

174 lines
6.8 KiB
Python
Executable File

#!/usr/bin/env python3
# open_agb_firm gba_db.bin Builder v3.0
# By HTV04
#
# This script parses MAME's gba.xml (found here: https://github.com/mamedev/mame/blob/master/hash/gba.xml) and converts it to a gba_db.bin file for open_agb_firm.
# No-Intro's GBA DAT (with scene numbers) is also used for filtering and naming (found here: https://datomatic.no-intro.org/). The DAT should be renamed to "gba.dat".
# Unless otherwise specified, entries from an addentries.csv file are also added. This file usually includes entries that cannot be not found or are wrong in MAME's gba.xml.
#
# This script should work with any updates to MAME's gba.xml and the No-Intro DAT, unless something this script expects is changed.
import csv
import math
import re
import sys
import xml.etree.ElementTree as ET
# Use title, serial, SHA-1, size, and save type to generate gba_db entry as binary string
def gbadbentry(title, serial, sha, size, savetype):
entry = []
if len(sha) != 40:
sha = '0000000000000000000000000000000000000000'
shabytes = bytes.fromhex(sha)
entry.append(int.from_bytes(shabytes[:8], 'little')) # Sorting key
entry.append(title.encode().ljust(200, b'\x00')[:200])
if len(serial) != 4 or bool(re.search('[^A-Z0-9]', serial)):
entry.append(b'\x00\x00\x00\x00')
else:
entry.append(serial.encode())
entry.append(shabytes)
entry.append((int(math.log(size, 2)) << 27 | savetype).to_bytes(4, 'little'))
return entry
# Prepare gba_db list for gba_db.bin
def preparegbadb(gbadb):
# Use sort key to sort the gba_db list and delete it from each entry
gbadb = sorted(gbadb, key=lambda l:l[0])
for i in range(len(gbadb)):
gbadb[i].pop(0)
# Compile gba_db binary
gbadbbin = b''
for i in gbadb:
for j in i:
gbadbbin += j
return gbadbbin
if __name__ == '__main__':
# Arguments (could totally be done better but this will do for now)
noaddentries = False
if len(sys.argv) >= 2 and sys.argv[1] == 'noaddentries': # Don't include anything that isn't in gba.xml and gba.dat
noaddentries = True
# Start adding entries
gbadb = []
skipcount = 0
count = 0
gba = ET.parse('gba.xml').getroot() # MAME gba.xml
nointro = ET.parse('gba.dat').getroot() # No-Intro GBA DAT
for software in gba.findall('software'):
for part in software.findall('part'):
if part.get('name') == 'cart':
# Obtain SHA-1
for dataarea in part.findall('dataarea'):
if dataarea.get('name') == 'rom':
sha = dataarea.find('rom').get('sha1')
break
# Obtain title, serial, SHA-1, and size from No-Intro DAT
matchfound = False
for game in nointro.findall('game'):
for rom in game.findall('rom'):
if rom.get('sha1').lower() == sha:
title = game.get('name')
serial = rom.get('serial')
if serial == None:
serial = ''
size = int(rom.get('size'))
matchfound = True
break
# If not in No-Intro DAT, skip entry
if not matchfound:
break
# Obtain save type
savetype = 15 # SAVE_TYPE_NONE
for feature in part.findall('feature'):
if feature.get('name') == 'slot':
slottype = feature.get('value')
if slottype in ('gba_eeprom_4k', 'gba_yoshiug', 'gba_eeprom'):
savetype = 0 # SAVE_TYPE_EEPROM_8k
if size > 0x1000000:
savetype += 1 # SAVE_TYPE_EEPROM_8k_2
elif slottype in ('gba_eeprom_64k', 'gba_boktai'):
savetype = 2 # SAVE_TYPE_EEPROM_64k
if size > 0x1000000:
savetype += 1 # SAVE_TYPE_EEPROM_64k_2
elif slottype == 'gba_flash_rtc':
savetype = 8 # SAVE_TYPE_FLASH_512k_PSC_RTC
elif slottype in ('gba_flash', 'gba_flash_512'):
savetype = 9 # SAVE_TYPE_FLASH_512k_PSC
elif slottype == 'gba_flash_1m_rtc':
savetype = 10 # SAVE_TYPE_FLASH_1m_MRX_RTC
elif slottype == 'gba_flash_1m':
savetype = 11 # SAVE_TYPE_FLASH_1m_MRX
elif slottype in ('gba_sram', 'gba_drilldoz', 'gba_wariotws'):
savetype = 14 # SAVE_TYPE_SRAM_256k
break
# If not in No-Intro DAT, skip entry
if not matchfound:
print ('Skipped "' + software.find('description').text + '"')
skipcount += 1
continue
# Add entry to gba_db
entry = gbadbentry(title, serial, sha, size, savetype)
for i in range(len(gbadb)):
if gbadb[i][3].hex() == sha:
print('Duplicate entry "' + gbadb[i][1].decode() + '" replaced')
gbadb[i] = entry
skipcount += 1
break
else:
gbadb.append(entry)
count += 1
print('Added entry "' + title + '"')
# Add additional entries from addentries.csv if "noaddentries" is false
if not noaddentries:
with open('addentries.csv') as f:
addentries = list(csv.reader(f))
addentries.pop(0)
for i in addentries:
i[3] = int(i[3])
i[4] = int(i[4])
print()
for title, serial, sha, size, savetype in addentries:
entry = gbadbentry(title, serial, sha, size, savetype)
for i in range(len(gbadb)):
if gbadb[i][3].hex().upper() == sha:
print('Duplicate entry "' + gbadb[i][1].decode() + '" replaced')
gbadb[i] = entry
skipcount += 1
break
else:
gbadb.append(entry)
count += 1
print('Added additional entry "' + title + '"')
gbadbbin = preparegbadb(gbadb)
# Create and write to gba_db.bin
with open('gba_db.bin', 'wb') as f:
f.write(gbadbbin)
print('\n' + str(count) + ' entries added, ' + str(skipcount) + ' entries skipped')