mirror of
https://github.com/lesserkuma/GBA_MultiMenu.git
synced 2026-08-24 01:24:23 -05:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f756a705ae | ||
|
|
d51437552e | ||
|
|
12d953d8fd |
@@ -51,6 +51,7 @@ In the `games` section, you can edit the game-related stuff:
|
|||||||
- `6` = Pokémon Black & White condensed battle font
|
- `6` = Pokémon Black & White condensed battle font
|
||||||
- `save_slot` defines which save slot your game uses. Set it to `null` for no saving or a number starting from `1`. Multiple games can share a save slot.
|
- `save_slot` defines which save slot your game uses. Set it to `null` for no saving or a number starting from `1`. Multiple games can share a save slot.
|
||||||
- `map_256m`, if set to `true`, can serve as a workaround for a glitch with the cartridge mapper that causes games to freeze with screeching noises upon launch.
|
- `map_256m`, if set to `true`, can serve as a workaround for a glitch with the cartridge mapper that causes games to freeze with screeching noises upon launch.
|
||||||
|
- `keys` will let you specify a list of keys that must be held down at startup for this ROM to appear in the menu, e. g. `[ "L", "R", "DOWN" ]`.
|
||||||
|
|
||||||
### ROM Builder Command Line Arguments
|
### ROM Builder Command Line Arguments
|
||||||
|
|
||||||
@@ -75,6 +76,8 @@ If the cartridge has a battery installed, the ROMs must be SRAM-patched with [GB
|
|||||||
|
|
||||||
If the cartridge has no battery installed, the ROMs must be patched for batteryless SRAM saving with maniac's [Automatic batteryless saving patcher](https://github.com/metroid-maniac/gba-auto-batteryless-patcher/).
|
If the cartridge has no battery installed, the ROMs must be patched for batteryless SRAM saving with maniac's [Automatic batteryless saving patcher](https://github.com/metroid-maniac/gba-auto-batteryless-patcher/).
|
||||||
|
|
||||||
|
On battery-equipped cartridges, when starting a game from the menu, the previously played game's save data will be read from SRAM and stored to permanent flash memory. To skip this, you can hold the SELECT button while starting the game.
|
||||||
|
|
||||||
## Compatibility
|
## Compatibility
|
||||||
Tested repro cartridges:
|
Tested repro cartridges:
|
||||||
- 100SOP with MSP55LV100S
|
- 100SOP with MSP55LV100S
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
import sys, os, glob, json, math, re, struct, hashlib, argparse, datetime
|
import sys, os, glob, json, math, re, struct, hashlib, argparse, datetime
|
||||||
|
|
||||||
# Configuration
|
# Configuration
|
||||||
app_version = "0.8"
|
app_version = "0.10"
|
||||||
default_file = "LK_MULTIMENU_<CODE>.gba"
|
default_file = "LK_MULTIMENU_<CODE>.gba"
|
||||||
|
|
||||||
################################
|
################################
|
||||||
@@ -145,6 +145,7 @@ block_size = cartridge_types[cartridge_type]["block_size"]
|
|||||||
block_count = flash_size // block_size
|
block_count = flash_size // block_size
|
||||||
sectors_per_block = 0x80000 // sector_size
|
sectors_per_block = 0x80000 // sector_size
|
||||||
compilation = bytearray()
|
compilation = bytearray()
|
||||||
|
roms_keys = [0]
|
||||||
for i in range(flash_size // 0x2000000):
|
for i in range(flash_size // 0x2000000):
|
||||||
chunk = bytearray([0xFF] * 0x2000000)
|
chunk = bytearray([0xFF] * 0x2000000)
|
||||||
compilation += chunk
|
compilation += chunk
|
||||||
@@ -199,6 +200,35 @@ for game in games:
|
|||||||
game["title_font"] = 0
|
game["title_font"] = 0
|
||||||
game["sector_count"] = int(size / sector_size)
|
game["sector_count"] = int(size / sector_size)
|
||||||
|
|
||||||
|
# Hidden ROMs
|
||||||
|
keys = 0
|
||||||
|
if "keys" in game:
|
||||||
|
for key in game["keys"]:
|
||||||
|
if key.upper() == "A":
|
||||||
|
keys |= (1 << 0)
|
||||||
|
elif key.upper() == "B":
|
||||||
|
keys |= (1 << 1)
|
||||||
|
elif key.upper() == "SELECT":
|
||||||
|
keys |= (1 << 2)
|
||||||
|
elif key.upper() == "START":
|
||||||
|
keys |= (1 << 3)
|
||||||
|
elif key.upper() == "RIGHT":
|
||||||
|
keys |= (1 << 4)
|
||||||
|
elif key.upper() == "LEFT":
|
||||||
|
keys |= (1 << 5)
|
||||||
|
elif key.upper() == "UP":
|
||||||
|
keys |= (1 << 6)
|
||||||
|
elif key.upper() == "DOWN":
|
||||||
|
keys |= (1 << 7)
|
||||||
|
elif key.upper() == "R":
|
||||||
|
keys |= (1 << 8)
|
||||||
|
elif key.upper() == "L":
|
||||||
|
keys |= (1 << 9)
|
||||||
|
game["keys"] = keys
|
||||||
|
if keys > 0:
|
||||||
|
roms_keys.append(keys)
|
||||||
|
roms_keys = list(set(roms_keys))
|
||||||
|
|
||||||
if battery_present and game["save_slot"] is not None:
|
if battery_present and game["save_slot"] is not None:
|
||||||
game["save_type"] = 2
|
game["save_type"] = 2
|
||||||
game["save_slot"] -= 1
|
game["save_slot"] -= 1
|
||||||
@@ -240,7 +270,6 @@ for game in games:
|
|||||||
|
|
||||||
# Read ROM data
|
# Read ROM data
|
||||||
games.sort(key=lambda game: game["size"], reverse=True)
|
games.sort(key=lambda game: game["size"], reverse=True)
|
||||||
c = 0
|
|
||||||
for game in games:
|
for game in games:
|
||||||
found = False
|
found = False
|
||||||
for i in range(save_end_offset, len(sector_map)):
|
for i in range(save_end_offset, len(sector_map)):
|
||||||
@@ -291,7 +320,12 @@ else:
|
|||||||
toc_sep = "----+-----------+-----------+--------------------------------------------------"
|
toc_sep = "----+-----------+-----------+--------------------------------------------------"
|
||||||
|
|
||||||
item_list = bytearray()
|
item_list = bytearray()
|
||||||
for game in games:
|
|
||||||
|
for key in roms_keys:
|
||||||
|
c = 0
|
||||||
|
for game in games:
|
||||||
|
if game["keys"] != key: continue
|
||||||
|
|
||||||
title = game["title"]
|
title = game["title"]
|
||||||
if len(title) > 0x30: title = title[:0x2F] + "…"
|
if len(title) > 0x30: title = title[:0x2F] + "…"
|
||||||
|
|
||||||
@@ -316,7 +350,8 @@ for game in games:
|
|||||||
item_list += bytearray(struct.pack("<H", game["block_count"]))
|
item_list += bytearray(struct.pack("<H", game["block_count"]))
|
||||||
item_list += bytearray(struct.pack("B", game["save_type"]))
|
item_list += bytearray(struct.pack("B", game["save_type"]))
|
||||||
item_list += bytearray(struct.pack("B", game["save_slot"]))
|
item_list += bytearray(struct.pack("B", game["save_slot"]))
|
||||||
item_list += bytearray([0] * 8)
|
item_list += bytearray(struct.pack("<H", game["keys"]))
|
||||||
|
item_list += bytearray([0] * 6)
|
||||||
item_list += bytearray(title.encode("UTF-16LE"))
|
item_list += bytearray(title.encode("UTF-16LE"))
|
||||||
|
|
||||||
compilation[item_list_offset * sector_size:item_list_offset * sector_size + len(item_list)] = item_list
|
compilation[item_list_offset * sector_size:item_list_offset * sector_size + len(item_list)] = item_list
|
||||||
|
|||||||
@@ -301,6 +301,7 @@ IWRAM_CODE u8 BootGame(ItemConfig config, FlashStatus status)
|
|||||||
FlashEraseSector((_flash_save_block_offset + status.last_boot_save_index) * _flash_sector_size);
|
FlashEraseSector((_flash_save_block_offset + status.last_boot_save_index) * _flash_sector_size);
|
||||||
FlashWriteData((_flash_save_block_offset + status.last_boot_save_index) * _flash_sector_size, SRAM_SIZE);
|
FlashWriteData((_flash_save_block_offset + status.last_boot_save_index) * _flash_sector_size, SRAM_SIZE);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Save status to flash
|
// Save status to flash
|
||||||
status.last_boot_save_index = config.save_index;
|
status.last_boot_save_index = config.save_index;
|
||||||
@@ -309,7 +310,6 @@ IWRAM_CODE u8 BootGame(ItemConfig config, FlashStatus status)
|
|||||||
memcpy(data_buffer, &status, sizeof(status));
|
memcpy(data_buffer, &status, sizeof(status));
|
||||||
FlashEraseSector(_flash_status_block_offset * flash_sector_size);
|
FlashEraseSector(_flash_status_block_offset * flash_sector_size);
|
||||||
FlashWriteData(_flash_status_block_offset * flash_sector_size, 0x1000);
|
FlashWriteData(_flash_status_block_offset * flash_sector_size, 0x1000);
|
||||||
}
|
|
||||||
|
|
||||||
// Disable SRAM access
|
// Disable SRAM access
|
||||||
*(vu8 *)MAPPER_CONFIG4 = 0;
|
*(vu8 *)MAPPER_CONFIG4 = 0;
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ int main(void) {
|
|||||||
u8 redraw_items = 0xFF;
|
u8 redraw_items = 0xFF;
|
||||||
u8 roms_page = 7;
|
u8 roms_page = 7;
|
||||||
u16 kHeld = 0;
|
u16 kHeld = 0;
|
||||||
|
u16 kHeld_boot = 0;
|
||||||
BOOL show_debug = FALSE;
|
BOOL show_debug = FALSE;
|
||||||
BOOL show_credits = FALSE;
|
BOOL show_credits = FALSE;
|
||||||
BOOL boot_failed = FALSE;
|
BOOL boot_failed = FALSE;
|
||||||
@@ -90,21 +91,32 @@ int main(void) {
|
|||||||
SetMode(MODE_4 | BG2_ENABLE);
|
SetMode(MODE_4 | BG2_ENABLE);
|
||||||
dmaCopy(bgBitmap, (void*)AGB_VRAM+0xA000, SCREEN_WIDTH * SCREEN_HEIGHT);
|
dmaCopy(bgBitmap, (void*)AGB_VRAM+0xA000, SCREEN_WIDTH * SCREEN_HEIGHT);
|
||||||
|
|
||||||
// Count number of ROMs
|
// Check on-boot keys
|
||||||
for (roms_total = 0; roms_total < 512; roms_total++) {
|
scanKeys();
|
||||||
if ((itemlist[(0x70*roms_total+1)] == 0) || (itemlist[(0x70*roms_total+1)] == 0xFF)) break;
|
kHeld = keysHeld();
|
||||||
|
kHeld_boot = kHeld;
|
||||||
|
if ((kHeld & KEY_SELECT) && (kHeld & KEY_R)) {
|
||||||
|
show_credits = TRUE;
|
||||||
|
} else if (kHeld & KEY_SELECT) {
|
||||||
|
show_debug = TRUE;
|
||||||
|
}
|
||||||
|
if (kHeld) {
|
||||||
|
u16 itemlist_offset = 0;
|
||||||
|
BOOL found_keys = FALSE;
|
||||||
|
for (itemlist_offset = 0; itemlist_offset < 0x924; itemlist_offset += 0x70) {
|
||||||
|
memcpy(&sItemConfig, ((u8*)itemlist)+itemlist_offset, sizeof(sItemConfig));
|
||||||
|
if (sItemConfig.title_length == 0) break;
|
||||||
|
if (sItemConfig.title_length == 0xFF) break;
|
||||||
|
if (sItemConfig.keys == kHeld) {
|
||||||
|
itemlist += itemlist_offset;
|
||||||
|
found_keys = TRUE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!found_keys) {
|
||||||
|
kHeld = 0;
|
||||||
}
|
}
|
||||||
if (roms_total == 0) {
|
|
||||||
LoadFont(2);
|
|
||||||
DrawText(0, 64, ALIGN_CENTER, u"Please use the ROM Builder to", 48, font, (void*)AGB_VRAM+0xA000, FALSE);
|
|
||||||
DrawText(0, 64 + sFontSpecs.max_height, ALIGN_CENTER, u"create your own compilation.", 48, font, (void*)AGB_VRAM+0xA000, FALSE);
|
|
||||||
LoadFont(0);
|
|
||||||
DrawText(0, 127, ALIGN_CENTER, u"https://github.com/lesserkuma/GBA_MultiMenu", 48, font, (void*)AGB_VRAM+0xA000, FALSE);
|
|
||||||
DrawText(14, SCREEN_HEIGHT - sFontSpecs.max_height - 3 - FontMarginBottom, ALIGN_RIGHT, u"No ROMs", 10, font, (void*)AGB_VRAM+0xA000, FALSE);
|
|
||||||
REG_DISPCNT ^= 0x0010;
|
|
||||||
while (1) { VBlankIntrWait(); }
|
|
||||||
}
|
}
|
||||||
page_total = (roms_total + 8.0 - 1) / 8.0;
|
|
||||||
|
|
||||||
memcpy(&sFlashStatus, (void *)(AGB_ROM + flash_status_sector_offset * flash_sector_size), sizeof(sFlashStatus));
|
memcpy(&sFlashStatus, (void *)(AGB_ROM + flash_status_sector_offset * flash_sector_size), sizeof(sFlashStatus));
|
||||||
if ((sFlashStatus.magic != MAGIC_FLASH_STATUS) || (sFlashStatus.last_boot_menu_index >= roms_total)) {
|
if ((sFlashStatus.magic != MAGIC_FLASH_STATUS) || (sFlashStatus.last_boot_menu_index >= roms_total)) {
|
||||||
@@ -119,14 +131,28 @@ int main(void) {
|
|||||||
page_active = sFlashStatus.last_boot_menu_index / 8;
|
page_active = sFlashStatus.last_boot_menu_index / 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check on-boot keys
|
// Count number of ROMs
|
||||||
scanKeys();
|
for (roms_total = 0; roms_total < 512; roms_total++) {
|
||||||
kHeld = keysHeld();
|
memcpy(&sItemConfig, ((u8*)itemlist)+(0x70*roms_total), sizeof(sItemConfig));
|
||||||
if ((kHeld & KEY_SELECT) && (kHeld & KEY_R)) {
|
if (sItemConfig.keys != kHeld) break;
|
||||||
show_credits = TRUE;
|
if (sItemConfig.title_length == 0) break;
|
||||||
} else if (kHeld & KEY_SELECT) {
|
if (sItemConfig.title_length == 0xFF) break;
|
||||||
show_debug = TRUE;
|
|
||||||
}
|
}
|
||||||
|
if (roms_total == 0) {
|
||||||
|
LoadFont(2);
|
||||||
|
DrawText(0, 64, ALIGN_CENTER, u"Please use the ROM Builder to", 48, font, (void*)AGB_VRAM+0xA000, FALSE);
|
||||||
|
DrawText(0, 64 + sFontSpecs.max_height, ALIGN_CENTER, u"create your own compilation.", 48, font, (void*)AGB_VRAM+0xA000, FALSE);
|
||||||
|
LoadFont(0);
|
||||||
|
DrawText(0, 127, ALIGN_CENTER, u"https://github.com/lesserkuma/GBA_MultiMenu", 48, font, (void*)AGB_VRAM+0xA000, FALSE);
|
||||||
|
DrawText(14, SCREEN_HEIGHT - sFontSpecs.max_height - 3 - FontMarginBottom, ALIGN_RIGHT, u"No ROMs", 10, font, (void*)AGB_VRAM+0xA000, FALSE);
|
||||||
|
REG_DISPCNT ^= 0x0010;
|
||||||
|
while (1) { VBlankIntrWait(); }
|
||||||
|
} else if (roms_total == 1) {
|
||||||
|
memcpy(&sItemConfig, ((u8*)itemlist), sizeof(sItemConfig));
|
||||||
|
u8 error_code = BootGame(sItemConfig, sFlashStatus);
|
||||||
|
boot_failed = error_code;
|
||||||
|
}
|
||||||
|
page_total = (roms_total + 8.0 - 1) / 8.0;
|
||||||
|
|
||||||
s32 wait = 0;
|
s32 wait = 0;
|
||||||
u8 f = 0;
|
u8 f = 0;
|
||||||
@@ -212,6 +238,11 @@ int main(void) {
|
|||||||
// Check for menu keys
|
// Check for menu keys
|
||||||
scanKeys();
|
scanKeys();
|
||||||
kHeld = keysHeld();
|
kHeld = keysHeld();
|
||||||
|
if ((kHeld_boot == 0) || (kHeld != kHeld_boot)) {
|
||||||
|
kHeld_boot = 0;
|
||||||
|
} else {
|
||||||
|
kHeld = 0;
|
||||||
|
}
|
||||||
if (kHeld != 0) {
|
if (kHeld != 0) {
|
||||||
wait++;
|
wait++;
|
||||||
} else {
|
} else {
|
||||||
@@ -233,13 +264,15 @@ int main(void) {
|
|||||||
sFlashStatus.last_boot_menu_index = page_active * 8 + cursor_pos;
|
sFlashStatus.last_boot_menu_index = page_active * 8 + cursor_pos;
|
||||||
if (!show_credits && !show_debug) {
|
if (!show_credits && !show_debug) {
|
||||||
LoadFont(0);
|
LoadFont(0);
|
||||||
if (sFlashStatus.battery_present == 1) {
|
|
||||||
DrawText(5, SCREEN_HEIGHT - sFontSpecs.max_height - 3 - FontMarginBottom, ALIGN_LEFT, u"Loading… Don't turn off the power!", 48, font, (void*)AGB_VRAM+0xA000, FALSE);
|
DrawText(5, SCREEN_HEIGHT - sFontSpecs.max_height - 3 - FontMarginBottom, ALIGN_LEFT, u"Loading… Don't turn off the power!", 48, font, (void*)AGB_VRAM+0xA000, FALSE);
|
||||||
}
|
|
||||||
REG_DISPCNT ^= 0x0010;
|
REG_DISPCNT ^= 0x0010;
|
||||||
dmaCopy((void*)AGB_VRAM+0xA000, (void*)AGB_VRAM, SCREEN_WIDTH * SCREEN_HEIGHT);
|
dmaCopy((void*)AGB_VRAM+0xA000, (void*)AGB_VRAM, SCREEN_WIDTH * SCREEN_HEIGHT);
|
||||||
REG_DISPCNT ^= 0x0010;
|
REG_DISPCNT ^= 0x0010;
|
||||||
}
|
}
|
||||||
|
if (kHeld & KEY_SELECT) {
|
||||||
|
// Skips reading latest save data from SRAM
|
||||||
|
sFlashStatus.last_boot_save_type = SRAM_NONE;
|
||||||
|
}
|
||||||
u8 error_code = BootGame(sItemConfig, sFlashStatus);
|
u8 error_code = BootGame(sItemConfig, sFlashStatus);
|
||||||
boot_failed = error_code;
|
boot_failed = error_code;
|
||||||
redraw_items = 0xFF;
|
redraw_items = 0xFF;
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ typedef struct ItemConfig_
|
|||||||
u16 rom_size;
|
u16 rom_size;
|
||||||
SAVE_TYPE save_type;
|
SAVE_TYPE save_type;
|
||||||
u8 save_index;
|
u8 save_index;
|
||||||
u16 index;
|
u16 keys;
|
||||||
u8 reserved[6];
|
u8 reserved[6];
|
||||||
u16 title[0x30];
|
u16 title[0x30];
|
||||||
} ItemConfig;
|
} ItemConfig;
|
||||||
|
|||||||
Reference in New Issue
Block a user