GBA database updates (#128)
This commit is contained in:
parent
0f55ad941c
commit
92c591da22
|
@ -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
|
||||
|
|
Binary file not shown.
|
@ -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:
|
||||
|
|
|
@ -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
|
|
|
@ -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')
|
|
@ -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)
|
|
@ -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
|
|
Loading…
Reference in New Issue