mirror of
https://gitee.com/anod/open_agb_firm.git
synced 2025-05-06 13:54:09 +08:00
174 lines
6.8 KiB
Python
Executable File
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')
|