diff --git a/.gitignore b/.gitignore index 2f4cb90..4cebb23 100644 --- a/.gitignore +++ b/.gitignore @@ -7,9 +7,10 @@ arm9/open_agb_firm9.bin arm11/open_agb_firm11.bin arm9/open_agb_firm9.elf arm11/open_agb_firm11.elf -tools/gba-db-builder/gba_db.bin -tools/gba-db-builder/gba.dat -tools/gba-db-builder/gba.xml +tools/gba-db/gba_db.log +tools/gba-db/gba.xml +tools/gba-db/gba.dat +tools/gba-db/gba_db.bin open_agb_firm.firm open_agb_firm*.7z TODO.txt diff --git a/resources/gba_db.bin b/resources/gba_db.bin index e0a4b49..7af1288 100644 Binary files a/resources/gba_db.bin and b/resources/gba_db.bin differ diff --git a/source/arm11/open_agb_firm.c b/source/arm11/open_agb_firm.c index d2ec625..7bd9b11 100644 --- a/source/arm11/open_agb_firm.c +++ b/source/arm11/open_agb_firm.c @@ -100,11 +100,11 @@ typedef struct typedef struct { - char name[200]; - char serial[4]; u8 sha1[20]; + char serial[4]; u32 attr; -} GameDbEntry; +} GbaDbEntry; +static_assert(sizeof(GbaDbEntry) == 28, "Error: GBA DB entry struct is not packed!"); // Default config. @@ -317,7 +317,7 @@ static u16 detectSaveType(u32 romSize) } // Search for entry with first u64 of the SHA1 = x using binary search. -static Result searchGbaDb(u64 x, GameDbEntry *const db, s32 *const entryPos) +static Result searchGbaDb(u64 x, GbaDbEntry *const db, s32 *const entryPos) { debug_printf("Database search: '%016" PRIX64 "'\n", __builtin_bswap64(x)); @@ -326,14 +326,14 @@ static Result searchGbaDb(u64 x, GameDbEntry *const db, s32 *const entryPos) if((res = fOpen(&f, "gba_db.bin", FA_OPEN_EXISTING | FA_READ)) == RES_OK) { s32 l = 0; - s32 r = fSize(f) / sizeof(GameDbEntry) - 1; // TODO: Check for 0! + s32 r = fSize(f) / sizeof(GbaDbEntry) - 1; // TODO: Check for 0! while(1) { const s32 mid = l + (r - l) / 2; debug_printf("l: %ld r: %ld mid: %ld\n", l, r, mid); - if((res = fLseek(f, sizeof(GameDbEntry) * mid)) != RES_OK) break; - if((res = fRead(f, db, sizeof(GameDbEntry), NULL)) != RES_OK) break; + if((res = fLseek(f, sizeof(GbaDbEntry) * mid)) != RES_OK) break; + if((res = fRead(f, db, sizeof(GbaDbEntry), NULL)) != RES_OK) break; const u64 tmp = *(u64*)db->sha1; // Unaligned access. if(tmp == x) { @@ -343,7 +343,7 @@ static Result searchGbaDb(u64 x, GameDbEntry *const db, s32 *const entryPos) if(r <= l) { - debug_printf("Not found!"); + debug_printf("Not found!\n"); res = RES_NOT_FOUND; break; } @@ -369,7 +369,7 @@ static u16 getSaveType(u32 romSize, const char *const savePath) sha((u32*)LGY_ROM_LOC, romSize, (u32*)sha1, SHA_IN_BIG | SHA_1_MODE, SHA_OUT_BIG); Result res; - GameDbEntry dbEntry; + GbaDbEntry dbEntry; s32 dbPos = -1; u16 saveType = SAVE_TYPE_NONE; res = searchGbaDb(*sha1, &dbEntry, &dbPos); @@ -383,72 +383,71 @@ static u16 getSaveType(u32 romSize, const char *const savePath) } debug_printf("saveType: %u\n", saveType); - if(saveOverride) + if(!saveOverride) goto end; + + consoleClear(); + ee_printf("==Save Type Override Menu==\n" + "Save file: %s\n" + "Save type (autodetected): %u\n" + "Save type (from gba_db.bin): ", (saveExists ? "Found" : "Not found"), autoSaveType); + if(res == RES_NOT_FOUND) + ee_puts("Not found"); + else + ee_printf("%u\n", saveType); + ee_puts("\n" + "=Save Types=\n" + " EEPROM 8k (0, 1)\n" + " EEPROM 64k (2, 3)\n" + " Flash 512k RTC (4, 6, 8)\n" + " Flash 512k (5, 7, 9)\n" + " Flash 1m RTC (10, 12)\n" + " Flash 1m (11, 13)\n" + " SRAM 256k (14)\n" + " None (15)\n\n" + "=Controls=\n" + "Up/Down: Navigate\n" + "A: Select\n" + "X: Delete save file"); + + static const u8 saveTypeCursorLut[16] = {0, 0, 1, 1, 2, 3, 2, 3, 2, 3, 4, 5, 4, 5, 6, 7}; + u8 oldCursor = 0; + u8 cursor; + if(!g_oafConfig.useGbaDb || res == RES_NOT_FOUND) + cursor = saveTypeCursorLut[autoSaveType]; + else + cursor = saveTypeCursorLut[saveType]; + while(1) { - consoleClear(); - ee_printf("==Save Type Override Menu==\n" - "Save file: %s\n" - "Save type (autodetected): %u\n" - "Save type (from gba_db.bin): ", (saveExists ? "Found" : "Not found"), autoSaveType); - if(res == RES_NOT_FOUND) - ee_puts("Not found"); - else - ee_printf("%u\n", saveType); - ee_puts("\n" - "=Save Types=\n" - " EEPROM 8k (0, 1)\n" - " EEPROM 64k (2, 3)\n" - " Flash 512k RTC (4, 6, 8)\n" - " Flash 512k (5, 7, 9)\n" - " Flash 1m RTC (10, 12)\n" - " Flash 1m (11, 13)\n" - " SRAM 256k (14)\n" - " None (15)\n\n" - "=Controls=\n" - "Up/Down: Navigate\n" - "A: Select\n" - "X: Delete save file"); + ee_printf("\x1b[%u;H ", oldCursor + 6); + ee_printf("\x1b[%u;H>", cursor + 6); + oldCursor = cursor; - static const u8 saveTypeCursorLut[16] = {0, 0, 1, 1, 2, 3, 2, 3, 2, 3, 4, 5, 4, 5, 6, 7}; - u8 oldCursor = 0; - u8 cursor; - if(!g_oafConfig.useGbaDb || res == RES_NOT_FOUND) - cursor = saveTypeCursorLut[autoSaveType]; - else - cursor = saveTypeCursorLut[saveType]; - while(1) + u32 kDown; + do { - ee_printf("\x1b[%u;H ", oldCursor + 6); - ee_printf("\x1b[%u;H>", cursor + 6); - oldCursor = cursor; + GFX_waitForVBlank0(); - u32 kDown; - do - { - GFX_waitForVBlank0(); + hidScanInput(); + if(hidGetExtraKeys(0) & (KEY_POWER_HELD | KEY_POWER)) goto end; + kDown = hidKeysDown(); + } while(kDown == 0); - hidScanInput(); - if(hidGetExtraKeys(0) & (KEY_POWER_HELD | KEY_POWER)) goto end; - kDown = hidKeysDown(); - } while(kDown == 0); - - if((kDown & KEY_DUP) && cursor > 0) cursor--; - else if((kDown & KEY_DDOWN) && cursor < 7) cursor++; - else if(kDown & KEY_X) - { - fUnlink(savePath); - ee_printf("\x1b[1;11HDeleted "); - } - else if(kDown & KEY_A) break; - } - - static const u8 cursorSaveTypeLut[8] = {0, 2, 8, 9, 10, 11, 14, 15}; - saveType = cursorSaveTypeLut[cursor]; - if(saveType == SAVE_TYPE_EEPROM_8k || saveType == SAVE_TYPE_EEPROM_64k) + if((kDown & KEY_DUP) && cursor > 0) cursor--; + else if((kDown & KEY_DDOWN) && cursor < 7) cursor++; + else if(kDown & KEY_X) { - // If ROM bigger than 16 MiB --> SAVE_TYPE_EEPROM_8k_2 or SAVE_TYPE_EEPROM_64k_2. - if(romSize > 0x1000000) saveType++; + fUnlink(savePath); + ee_printf("\x1b[1;11HDeleted "); } + else if(kDown & KEY_A) break; + } + + static const u8 cursorSaveTypeLut[8] = {0, 2, 8, 9, 10, 11, 14, 15}; + saveType = cursorSaveTypeLut[cursor]; + if(saveType == SAVE_TYPE_EEPROM_8k || saveType == SAVE_TYPE_EEPROM_64k) + { + // If ROM bigger than 16 MiB --> SAVE_TYPE_EEPROM_8k_2 or SAVE_TYPE_EEPROM_64k_2. + if(romSize > 0x1000000) saveType++; } end: diff --git a/tools/gba-db-builder/addentries.csv b/tools/gba-db-builder/addentries.csv deleted file mode 100644 index dad28d5..0000000 --- a/tools/gba-db-builder/addentries.csv +++ /dev/null @@ -1,21 +0,0 @@ -Title,Serial,SHA-1,Size,Save Type -0246 - Kinniku Banzuke - Kimero! Kiseki no Kanzen Seiha (Japan),AK5J,CF0A6C1C473BA6C85027B6071AA1CF6E21336974,8388608,14 -1364 - Famicom Mini 01 - Super Mario Bros. (Japan) (En) (Rev 1),FSMJ,F08B1F60E41FC2080C50C65EA2B2AF912661ED99,1048576,0 -1366 - Famicom Mini 02 - Donkey Kong (Japan) (En),FDKJ,B5AFC36A8203C2C485344819D069300ECAFD9657,1048576,0 -"1492 - Classic NES Series - The Legend of Zelda (USA, Europe)",FZLE,28AAC26365BF41BA84E67F97E98D15C4678CB99D,1048576,2 -"1494 - Classic NES Series - Super Mario Bros. (USA, Europe)",FSME,8CA35864AE33C9462DD66CEFF1FAC5A79E2E0A6F,1048576,0 -"1496 - Classic NES Series - Bomberman (USA, Europe)",FBME,741EB2874C526CC014BF3E642B4EE37F18312735,1048576,0 -"1498 - Classic NES Series - Xevious (USA, Europe)",FXVE,B2088582808480E0D70C63A777B046409D4E15C4,1048576,0 -"1499 - Classic NES Series - Pac-Man (USA, Europe)",FP7E,843D853ED28A116C85A5357F9A94E9179F36A6D0,1048576,0 -"1500 - Classic NES Series - Ice Climber (USA, Europe)",FICE,64E965D61B2D1BE5DFADB0236FED83FEA5995724,1048576,0 -"1501 - Classic NES Series - Donkey Kong (USA, Europe)",FDKE,8E3B203630F10C32AA7896AFE9B7FBED7E1C8D30,1048576,0 -"1721 - Classic NES Series - Zelda II - The Adventure of Link (USA, Europe)",FLBE,22EA42AD9A99A6BC0FCBA8721CF8F484F68C3BF7,1048576,2 -1722 - Classic NES Series - Castlevania (USA),FADE,47A60315ED4074A8C986723B95B3C90513D3B35C,1048576,0 -"1723 - Classic NES Series - Dr. Mario (USA, Europe)",FDME,FC396F0EAE55CF19E573AA322F525427E03D3854,1048576,0 -"1724 - Classic NES Series - Metroid (USA, Europe)",FMRE,838DA11F879E2FA3FFDEF95E1C6D5A54246C1846,1048576,0 -2830 - NES Classics - Castlevania (Europe),FADP,8BC8740A681E4D365419DBCEE73619FE4429D66E,1048576,0 -"2841 - King Kong - The Official Game of the Movie (Europe) (En,Sv,No,Da,Fi)",BKQX,ECFB6409ABB7FF429AADC65A6B953DBAEF3D09D8,8388608,0 -2846 - Dogz 2 (USA) (Rev 1),BIME,FF2772E347212264D1A1C4D322A08685ADC1E6A7,16777216,2 -x025 - Famicom Mini - Dai-2-ji Super Robot Taisen (Japan) (Promo),FSRJ,8FA03B1E23E7DEA0DBC841D23046D03D246A1CDF,1048576,2 -x026 - Famicom Mini - Kidou Senshi Z Gundam - Hot Scramble (Japan) (Promo),FGZJ,B4C1A3582F596D3912A1A7EA2D1EB6681B769448,1048576,0 -x027 - Super Mario Bros. (Japan) (Hot Mario Campaign),FSMJ,6701010F7C195CF3FBDEDB19E4627835A3158748,1048576,15 diff --git a/tools/gba-db-builder/gba-db-builder.py b/tools/gba-db-builder/gba-db-builder.py deleted file mode 100755 index c59ef9f..0000000 --- a/tools/gba-db-builder/gba-db-builder.py +++ /dev/null @@ -1,173 +0,0 @@ -#!/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') diff --git a/tools/gba-db/gba-db.py b/tools/gba-db/gba-db.py new file mode 100755 index 0000000..bce7627 --- /dev/null +++ b/tools/gba-db/gba-db.py @@ -0,0 +1,239 @@ +#!/usr/bin/env python3 + +# open_agb_firm gba_db.bin Builder v4.0 +# By HTV04 +# +# This script parses MAME's "gba.xml" (https://github.com/mamedev/mame/blob/master/hash/gba.xml) +# and converts it to a "gba_db.bin" file for open_agb_firm. +# +# The official gba_db.bin is built using "--dat" and "--csv." The former uses No-Intro's "gba.dat" +# (https://datomatic.no-intro.org/) to verify the SHA-1 and size of each entry. The latter uses +# "gba.csv" to add additional entries and overrides. +# +# Note that, for efficiency, this script does not check for formatting errors and assumes that all +# entries are valid. Errors may occur otherwise. + +# MIT License +# +# Copyright (c) 2023 HTV04 +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +import enum +import sys +import re +import xml.etree.ElementTree +import csv + +class Entry: + def __init__(self): + self.sha1 = '\x00' * 20 + self.serial = '\x00' * 4 + self.attr = '\x00' * 4 + +class Database: + def __init__(self, log): + class SaveType(enum.IntEnum): + EEPROM_8K = 0 + EEPROM_8K_2 = 1 + EEPROM_64K = 2 + EEPROM_64K_2 = 3 + FLASH_512K_PSC_RTC = 8 + FLASH_512K_PSC = 9 + FLASH_1M_MRX_RTC = 10 + FLASH_1M_MRX = 11 + SRAM_256K = 14 + NONE = 15 + + class XmlData: + def __init__(self, software): + def find_name(node, child, name): + for found in node.findall(child): + if found.get('name') == name: + return found + else: + return None + + cart = find_name(software, 'part', 'cart') + serial = find_name(software, 'info', 'serial') + if serial is not None: + serial = re.search(r'[A-Z0-9]{4}', serial.get('value')) + rom = find_name(cart, 'dataarea', 'rom').find('rom') + size = int(rom.get('size'), base=0) + slot = find_name(cart, 'feature', 'slot') + save_type = SaveType.NONE + if slot is not None: + match slot.get('value'): + case 'gba_eeprom_4k' | 'gba_yoshiug' | 'gba_eeprom': + if size > 0x1000000: + save_type = SaveType.EEPROM_8K_2 + else: + save_type = SaveType.EEPROM_8K + case 'gba_eeprom_64k' | 'gba_boktai': + if size > 0x1000000: + save_type = SaveType.EEPROM_64K_2 + else: + save_type = SaveType.EEPROM_64K + case 'gba_flash_rtc': + save_type = SaveType.FLASH_512K_PSC_RTC + case 'gba_flash' | 'gba_flash_512': + save_type = SaveType.FLASH_512K_PSC + case 'gba_flash_1m_rtc': + save_type = SaveType.FLASH_1M_MRX_RTC + case 'gba_flash_1m': + save_type = SaveType.FLASH_1M_MRX + case 'gba_sram' | 'gba_drilldoz' | 'gba_wariotws': + save_type = SaveType.SRAM_256K + + self.sha1 = bytes.fromhex(rom.get('sha1')) + if serial is None: + self.serial = b'\x00\x00\x00\x00' + else: + self.serial = serial.group().encode('ascii') + self.attr = int(save_type).to_bytes(4, byteorder='little') + + class DatData: + def __init__(self, dat, xml_data): + sha1 = xml_data.sha1.hex() + + self.exists = True + for game in dat.findall('game'): + rom = game.find('rom') + + if rom.get('sha1') == sha1: + serial = rom.find('serial') + + self.size = int(rom.get('size')) + if serial is None: + self.serial = xml_data.serial + else: + self.serial = serial.encode('ascii') + + break + else: + self.exists = False + + def get_entry(software, dat): + entry = Entry() + xml_data = XmlData(software) + + entry.sha1 = xml_data.sha1 + for test in self.entries: + if test.sha1 == entry.sha1: + return 'Duplicate SHA-1 "' + entry.sha1.hex() + '" ' + entry.attr = xml_data.attr + + if dat is None: + entry.serial = xml_data.serial + else: + dat_data = DatData(dat, xml_data) + if dat_data.exists: + entry.serial = dat_data.serial + else: + return 'SHA-1 "' + entry.sha1.hex() + '" not found in No-Intro DAT' + + if len(entry.sha1) + len(entry.serial) + len(entry.attr) != 28: + raise Exception('Database size is not a multiple of 28') + + return entry + + self.entries = [] + + log('Starting log...\n\n') + + fail_count = 0 + count = 0 + + if '--dat' in sys.argv: + dat = xml.etree.ElementTree.parse('gba.dat').getroot() + log('Using gba.dat!\n\n') + else: + dat = None + + for software in xml.etree.ElementTree.parse('gba.xml').getroot().findall('software'): + log('Adding "' + software.find('description').text + '"\n') + ret = get_entry(software, dat) + if isinstance(ret, Entry): + count += 1 + self.entries.append(ret) + log('Successfully added entry: ' + ret.sha1.hex()) + else: + fail_count += 1 + log('Failed to add entry: ' + ret) + log('\n\n') + + if '--csv' in sys.argv: + log('Using gba.csv!\n\n') + + with open('gba.csv') as f: + for sha1, serial, save_type in list(csv.reader(f))[1:]: + entry = Entry() + entry.sha1 = bytes.fromhex(sha1) + entry.serial = serial.encode('ascii') + entry.attr = int(SaveType(int(save_type))).to_bytes(4, byteorder='little') + + for i in range(len(self.entries)): + if self.entries[i].sha1 == entry.sha1: + self.entries[i] = entry + log('Duplicate SHA-1 "' + entry.sha1.hex() + '," replaced') + break + else: + count += 1 + self.entries.append(entry) + log('Added "' + entry.sha1.hex() + '"') + log('\n') + log('\n') + + log('Compiled with ' + str(count) + ' entries, ' + str(fail_count) + ' failures.\n') + + def compile(self, out): + for entry in sorted(self.entries, key=lambda a: int.from_bytes(a.sha1[:8], byteorder='little')): + out(entry.sha1) + out(entry.serial) + out(entry.attr) + +if __name__ == '__main__': + if '--help' in sys.argv: + print('open_agb_firm gba_db.bin Builder v4.0') + print('By HTV04') + print() + print('Usage: gba-db.py [options]') + print(' --log: Write log to "gba_db.log"') + print() + print(' --dat: Use No-Intro "gba.dat" for verification') + print(' --csv: Use "gba.csv" for additional entries and overrides') + print() + print(' --out [file]: Output to [file] instead of "gba_db.bin"') + print() + print(' --help: Display this help message and exit') + + sys.exit(0) + + if '--log' in sys.argv: + with open('gba_db.log', 'w') as f: + db = Database(f.write) + else: + db = Database(lambda text: None) + + # Compile to gba_db.bin + out = 'gba_db.bin' + if '--out' in sys.argv: + out = sys.argv[sys.argv.index('--out') + 1] + with open(out, 'wb') as f: + db.compile(f.write) diff --git a/tools/gba-db/gba.csv b/tools/gba-db/gba.csv new file mode 100644 index 0000000..56c8bff --- /dev/null +++ b/tools/gba-db/gba.csv @@ -0,0 +1,21 @@ +SHA-1,Serial,Save Type +cf0a6c1c473ba6c85027b6071aa1cf6e21336974,AK5J,14 +f08b1f60e41fc2080c50c65ea2b2af912661ed99,FSMJ,0 +b5afc36a8203c2c485344819d069300ecafd9657,FDKJ,0 +28aac26365bf41ba84e67f97e98d15c4678cb99d,FZLE,2 +8ca35864ae33c9462dd66ceff1fac5a79e2e0a6f,FSME,0 +741eb2874c526cc014bf3e642b4ee37f18312735,FBME,0 +b2088582808480e0d70c63a777b046409d4e15c4,FXVE,0 +843d853ed28a116c85a5357f9a94e9179f36a6d0,FP7E,0 +64e965d61b2d1be5dfadb0236fed83fea5995724,FICE,0 +8e3b203630f10c32aa7896afe9b7fbed7e1c8d30,FDKE,0 +22ea42ad9a99a6bc0fcba8721cf8f484f68c3bf7,FLBE,2 +47a60315ed4074a8c986723b95b3c90513d3b35c,FADE,0 +fc396f0eae55cf19e573aa322f525427e03d3854,FDME,0 +838da11f879e2fa3ffdef95e1c6d5a54246c1846,FMRE,0 +8bc8740a681e4d365419dbcee73619fe4429d66e,FADP,0 +ecfb6409abb7ff429aadc65a6b953dbaef3d09d8,BKQX,0 +ff2772e347212264d1a1c4d322a08685adc1e6a7,BIME,2 +8fa03b1e23e7dea0dbc841d23046d03d246a1cdf,FSRJ,2 +b4c1a3582f596d3912a1a7ea2d1eb6681b769448,FGZJ,0 +6701010f7c195cf3fbdedb19e4627835a3158748,FSMJ,15