8 Commits
0.6 ... 1.0

Author SHA1 Message Date
Lesserkuma
89b22c8205 1.0 2024-02-07 16:12:44 +01:00
Lesserkuma
39ab8936ec - 2024-01-06 19:27:44 +01:00
Lesserkuma
a2fa155e8e fixes 2024-01-06 14:16:48 +01:00
Lesserkuma
f756a705ae New config option + autoboot if there is only one game in the list 2024-01-06 11:47:03 +01:00
Lesserkuma
d51437552e - 2023-12-17 01:03:38 +01:00
Lesserkuma
12d953d8fd - 2023-12-17 00:46:37 +01:00
Lesserkuma
1bf4927d6b Support for MSP54LV100 2023-11-24 18:05:40 +01:00
Lesserkuma
a3c4a44893 0.7 2023-11-01 20:36:02 +01:00
8 changed files with 279 additions and 81 deletions

View File

@@ -183,7 +183,9 @@ $(shell touch $(CURDIR)/../$(SOURCES)/version.h)
%.gba: %.elf
@$(OBJCOPY) -O binary $< $@
@gbafix $@ "-tLK MULTIMENU" "-cAGBJ" "-mLK" "-r0"
@echo Copying to ROM builder folder
@cp $@ "$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))/rom_builder/lk_multimenu.gba"
@echo Done!
#---------------------------------------------------------------------------------------
endif

View File

@@ -15,15 +15,20 @@ The following section must be edited in order to specify the cartridge type to u
```json
"cartridge": {
"type": 2,
"battery_present": false
"battery_present": false,
"min_rom_size": 4194304
},
```
Set `type` to `1` or `2`:
- `1` = MSP55LV100S (e.g. The Legend of Zelda Collection - Classic Edition 7-in-1)
- `2` = 6600M0U0BE (e.g. 369IN1 2048M)
- `1` = MSP55LV100S (e.g. The Legend of Zelda Collection - Classic Edition 7-in-1, 64 MiB)
- `2` = 6600M0U0BE (e.g. 369IN1 2048M, 256 MiB)
- `3` = MSP54LV100 (e.g. The Legend of Zelda Collection - Classic Edition 7-in-1, 128 MiB)
- `4` = F0095H0 (e.g. 53 in one 4G, 512 MiB)
Set `battery_present` to `true` or `false`. This will enable enhanced save data handling which will only be functional with a working battery.
Set `min_rom_size` to whatever your cartridge supports as the smallest possible ROM size. Many newer cartridges only support ROMs no smaller than 4 MiB (`4194304`) while some older cartridges can go as low as 512 KiB (`524288`).
In the `games` section, you can edit the game-related stuff:
```json
"games": [
@@ -47,6 +52,7 @@ In the `games` section, you can edit the game-related stuff:
- `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.
- `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
@@ -57,6 +63,7 @@ No command line arguments are required for creating a compilation, however there
--no-wait don't wait for user input when finished
--no-log don't write a log file
--config config.json sets the config file to use
--bg bg.png sets the background image to use
--output output.gba sets the file name of the compilation ROM
```
@@ -71,15 +78,19 @@ 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/).
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
Tested repro cartridges:
- 100BS6600_48BALL_V4 with 6600M0U0BE
- 100SOP with MSP55LV100S
- 100BS6600_48BALL_V4 with 6600M0U0BE
- SUN100S_MSP54_XXX_BGA48 with MSP54LV100
- F0095_4G_V1 with F0095H0
The generated compilation ROM can be written and read using a [GBxCart RW v1.4+](https://www.gbxcart.com/) device by insideGadgets and the [FlashGBX](https://github.com/lesserkuma/FlashGBX) software.
## Thanks
Thanks to FraX, Ausar, liuyunx, BennVenn
Thanks to FraX, Ausar, liuyunx, BennVenn, Jenetrix, Matt
## Screenshots

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 756 B

BIN
rom_builder/bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 756 B

View File

@@ -5,7 +5,7 @@
import sys, os, glob, json, math, re, struct, hashlib, argparse, datetime
# Configuration
app_version = "0.6"
app_version = "1.0"
default_file = "LK_MULTIMENU_<CODE>.gba"
################################
@@ -14,7 +14,6 @@ def UpdateSectorMap(start, length, c):
sector_map[start + 1:start + length] = c * (length - 1)
sector_map[start] = c.upper()
def formatFileSize(size):
if size == 1:
return "{:d} Byte".format(size)
@@ -51,6 +50,18 @@ cartridge_types = [
"sector_size":0x40000,
"block_size":0x80000,
},
{
"name":"MSP54LV100",
"flash_size":0x8000000,
"sector_size":0x20000,
"block_size":0x80000,
},
{
"name":"F0095H0",
"flash_size":0x20000000,
"sector_size":0x40000,
"block_size":0x80000,
},
]
now = datetime.datetime.now()
log = ""
@@ -62,6 +73,7 @@ parser.add_argument("--split", help="splits output files into 32 MiB parts", act
parser.add_argument("--no-wait", help="dont wait for user input when finished", action="store_true", default=False)
parser.add_argument("--no-log", help="dont write a log file", action="store_true", default=False)
parser.add_argument("--config", type=str, default="config.json", help="sets the config file to use")
parser.add_argument("--bg", type=str, help="sets the background image to use")
parser.add_argument("--output", type=str, default=default_file, help="sets the file name of the compilation ROM")
args = parser.parse_args()
output_file = args.output
@@ -83,6 +95,7 @@ if not os.path.exists(args.config):
games = []
cartridge_type = 1
battery_present = False
min_rom_size = 0x400000
for file in files:
d = {
"enabled": True,
@@ -102,6 +115,7 @@ if not os.path.exists(args.config):
"cartridge": {
"type": cartridge_type + 1,
"battery_present": battery_present,
"min_rom_size": min_rom_size,
},
"games": games,
}
@@ -124,6 +138,10 @@ else:
games = j["games"]
cartridge_type = j["cartridge"]["type"] - 1
battery_present = j["cartridge"]["battery_present"]
if "min_rom_size" in j["cartridge"]:
min_rom_size = j["cartridge"]["min_rom_size"]
else:
min_rom_size = 0x400000
# Prepare compilation
flash_size = cartridge_types[cartridge_type]["flash_size"]
@@ -133,6 +151,7 @@ block_size = cartridge_types[cartridge_type]["block_size"]
block_count = flash_size // block_size
sectors_per_block = 0x80000 // sector_size
compilation = bytearray()
roms_keys = [0]
for i in range(flash_size // 0x2000000):
chunk = bytearray([0xFF] * 0x2000000)
compilation += chunk
@@ -140,7 +159,36 @@ sector_map = list("." * sector_count)
# Read menu ROM
with open("lk_multimenu.gba", "rb") as f:
menu_rom = f.read()
menu_rom = bytearray(f.read())
menu_rom += bytearray([0xFF] * ((len(menu_rom) + 0x10 - (len(menu_rom) % 0x10)) - len(menu_rom)))
menu_rom += bytearray([0xFF] * 0x20)
build_timestamp_offset = len(menu_rom) - 0x20
build_timestamp = datetime.datetime.now().astimezone().replace(microsecond=0).isoformat().encode("ASCII")
menu_rom[build_timestamp_offset:build_timestamp_offset+len(build_timestamp)] = build_timestamp
# Change background image
if args.bg or os.path.exists("bg.png"):
try:
from PIL import Image
if args.bg:
img = Image.open(args.bg)
else:
img = Image.open("bg.png")
img = img.convert('P')
palette = img.getpalette()
palette_rgb555 = [((b >> 3) << 10) | ((g >> 3) << 5) | (r >> 3) for r, g, b in zip(palette[::3], palette[1::3], palette[2::3])]
raw_bitmap = bytearray(list(img.tobytes()))
raw_palette = bytearray(0x200)
pos = 0
for color in palette_rgb555:
raw_palette[pos:pos+2] = struct.pack("<H", color)
pos += 2
menu_rom_bg_offset = menu_rom.find(b"RTFN\xFF\xFE") - 0x9800
menu_rom[menu_rom_bg_offset:menu_rom_bg_offset+0x9600] = raw_bitmap
menu_rom[menu_rom_bg_offset+0x9600:menu_rom_bg_offset+0x9800] = raw_palette
except ImportError:
print("Error: Couldnt update background image. Pillow library is not installed.")
menu_rom_size = menu_rom.find(b"dkARM\0\0\0") + 8
compilation[0:len(menu_rom)] = menu_rom
UpdateSectorMap(start=0, length=math.ceil(len(menu_rom) / sector_size), c="m")
@@ -176,7 +224,9 @@ for game in games:
with open(f"roms/{game['file']}", "rb") as f:
buffer = f.read()
if b"Batteryless mod by Lesserkuma" in buffer:
size = 0x400000
size = max(0x400000, min_rom_size)
else:
size = max(size, min_rom_size)
game["index"] = index
game["size"] = size
if "title_font" in game:
@@ -184,7 +234,37 @@ for game in games:
else:
game["title_font"] = 0
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:
game["save_type"] = 2
game["save_slot"] -= 1
@@ -226,7 +306,6 @@ for game in games:
# Read ROM data
games.sort(key=lambda game: game["size"], reverse=True)
c = 0
for game in games:
found = False
for i in range(save_end_offset, len(sector_map)):
@@ -270,40 +349,51 @@ logp("{:.2f}% ({:d} of {:d} sectors) used\n".format(sectors_used / sector_count
logp(f"Added {len(games)} ROM(s) to the compilation\n")
if battery_present:
logp (" | Offset | Map Size | Save Slot | Title")
toc_sep = "----+-----------+-----------+----------------+---------------------------------"
logp (" | Offset | Map Size | Save Slot | Title")
toc_sep = "----+------------+-----------+----------------+--------------------------------"
else:
logp (" | Offset | Map Size | Title")
toc_sep = "----+-----------+-----------+--------------------------------------------------"
logp (" | Offset | Map Size | Title")
toc_sep = "----+------------+-----------+-------------------------------------------------"
item_list = bytearray()
for game in games:
title = game["title"]
if len(title) > 0x30: title = title[:0x2F] + ""
table_line = \
f"{game['index'] + 1:3d} | " \
f"0x{game['block_offset'] * block_size:07X} | "\
f"0x{game['block_count'] * block_size:07X} | "
if battery_present:
if game['save_type'] > 0:
table_line += f"{game['save_slot']+1:2d} (0x{(save_data_sector_offset + game['save_slot']) * sector_size:07X}) | "
else:
table_line += " | "
table_line += f"{title}"
if c % 8 == 0: logp(toc_sep)
logp(table_line)
c += 1
title = title.ljust(0x30, "\0")
item_list += bytearray(struct.pack("B", game["title_font"]))
item_list += bytearray(struct.pack("B", len(game["title"])))
item_list += bytearray(struct.pack("<H", game["block_offset"]))
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_slot"]))
item_list += bytearray([0] * 8)
item_list += bytearray(title.encode("UTF-16LE"))
for key in roms_keys:
c = 0
for game in games:
if game["keys"] != key: continue
title = game["title"]
if len(title) > 0x30: title = title[:0x2F] + ""
table_line = \
f"{game['index'] + 1:3d} | " + \
f"0x{game['block_offset'] * block_size:X} | ".rjust(13, " ") + \
f"0x{game['block_count'] * block_size:X} | ".rjust(12, " ")
if battery_present:
if game['save_type'] > 0:
table_line += f"{game['save_slot']+1:2d} (0x{(save_data_sector_offset + game['save_slot']) * sector_size:07X}) | "
else:
table_line += " | "
table_line += f"{title}"
if c % 8 == 0:
if game['keys'] != 0:
temp = toc_sep[:-9] + "[Hidden]-"
logp(temp)
else:
logp(toc_sep)
logp(table_line)
c += 1
title = title.ljust(0x30, "\0")
item_list += bytearray(struct.pack("B", game["title_font"]))
item_list += bytearray(struct.pack("B", len(game["title"])))
item_list += bytearray(struct.pack("<H", game["block_offset"]))
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_slot"]))
item_list += bytearray(struct.pack("<H", game["keys"]))
item_list += bytearray([0] * 6)
item_list += bytearray(title.encode("UTF-16LE"))
compilation[item_list_offset * sector_size:item_list_offset * sector_size + len(item_list)] = item_list
rom_code = "L{:s}".format(hashlib.sha1(status + item_list).hexdigest()[:3]).upper()
@@ -317,14 +407,15 @@ for i in range(0xA0, 0xBD):
checksum = (checksum - 0x19) & 0xFF
compilation[0xBD] = checksum
logp("")
logp("Menu ROM: 0x{:07X}0x{:07X}".format(0, len(menu_rom)))
logp("Game List: 0x{:07X}0x{:07X}".format(item_list_offset * sector_size, item_list_offset * sector_size + len(item_list)))
logp("Status Area: 0x{:07X}0x{:07X}".format(status_offset * sector_size, status_offset * sector_size + 0x1000))
logp("Menu ROM: 0x{:08X}0x{:08X}".format(0, len(menu_rom)))
logp("Game List: 0x{:08X}0x{:08X}".format(item_list_offset * sector_size, item_list_offset * sector_size + len(item_list)))
logp("Status Area: 0x{:08X}0x{:08X}".format(status_offset * sector_size, status_offset * sector_size + 0x1000))
logp("")
logp("Cartridge Type: {:d} ({:s}) {:s}".format(cartridge_type + 1, cartridge_types[cartridge_type]["name"], "with battery" if battery_present else "without battery"))
logp("Output ROM Size: {:.2f} MiB".format(rom_size / 1024 / 1024))
logp("Output ROM Code: {:s}".format(rom_code))
output_file = output_file.replace("<CODE>", rom_code)
if args.split:
for i in range(0, math.ceil(flash_size / 0x2000000)):
pos = i * 0x2000000

View File

@@ -12,6 +12,7 @@ Author: Lesserkuma (github.com/lesserkuma)
u8 flash_type;
u8 *itemlist;
u16 itemlist_offset;
u32 flash_sector_size;
u32 flash_itemlist_sector_offset;
u32 flash_status_sector_offset;
@@ -66,6 +67,22 @@ IWRAM_CODE void FlashDetectType(void)
return;
}
// 1G cart with MSP54LV100S (Zelda Classic Collection 7-in-1)
_FLASH_WRITE(0, 0xF0);
_FLASH_WRITE(0xAAA, 0xA9);
_FLASH_WRITE(0x555, 0x56);
_FLASH_WRITE(0xAAA, 0x90);
data = *(vu32 *)AGB_ROM;
_FLASH_WRITE(0, 0xF0);
if (data == 0x227D0002)
{
REG_IE = ie;
flash_type = 3;
flash_sector_size = 0x20000;
FlashCalcOffsets();
return;
}
// Unknown type
REG_IE = ie;
flash_type = 0;
@@ -119,6 +136,24 @@ IWRAM_CODE void FlashEraseSector(u32 address)
}
_FLASH_WRITE(address, 0xF0F0);
}
else if (_flash_type == 3)
{
_FLASH_WRITE(0xAAA, 0xA9);
_FLASH_WRITE(0x555, 0x56);
_FLASH_WRITE(0xAAA, 0x80);
_FLASH_WRITE(0xAAA, 0xA9);
_FLASH_WRITE(0x555, 0x56);
_FLASH_WRITE(address, 0x30);
while (1)
{
__asm("nop");
if ((*((vu16 *)(AGB_ROM + address))) == 0xFFFF)
{
break;
}
}
_FLASH_WRITE(address, 0xF0);
}
REG_IE = ie;
}
@@ -191,6 +226,33 @@ IWRAM_CODE void FlashWriteData(u32 address, u32 length)
}
_FLASH_WRITE(address, 0xF0F0);
}
else if (_flash_type == 3)
{
for (int j = 0; j < (int)(length / 0x40); j++)
{
_FLASH_WRITE(0xAAA, 0xA9);
_FLASH_WRITE(0x555, 0x56);
_FLASH_WRITE(address + (j * 0x40), 0x26);
_FLASH_WRITE(address + (j * 0x40), 0x1F);
u16 data = 0;
for (int i = 0; i < 0x40; i += 2)
{
__asm("nop");
data = data_buffer[(j * 0x40) + i + 1] << 8 | data_buffer[(j * 0x40) + i];
_FLASH_WRITE(address + (j * 0x40) + i, data);
}
_FLASH_WRITE(address + (j * 0x40), 0x2A);
while (1)
{
__asm("nop");
if (p_rom[(j * 0x20) + 0x1F] == data)
{
break;
}
}
}
_FLASH_WRITE(address, 0xF0);
}
REG_IE = ie;
}
@@ -240,16 +302,16 @@ IWRAM_CODE u8 BootGame(ItemConfig config, FlashStatus status)
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);
}
// Save status to flash
status.last_boot_save_index = config.save_index;
status.last_boot_save_type = config.save_type;
memset((void *)data_buffer, 0, 0x1000);
memcpy(data_buffer, &status, sizeof(status));
FlashEraseSector(_flash_status_block_offset * flash_sector_size);
FlashWriteData(_flash_status_block_offset * flash_sector_size, 0x1000);
}
// Save status to flash
status.last_boot_save_index = config.save_index;
status.last_boot_save_type = config.save_type;
memset((void *)data_buffer, 0, 0x1000);
memcpy(data_buffer, &status, sizeof(status));
FlashEraseSector(_flash_status_block_offset * flash_sector_size);
FlashWriteData(_flash_status_block_offset * flash_sector_size, 0x1000);
// Disable SRAM access
*(vu8 *)MAPPER_CONFIG4 = 0;

View File

@@ -27,6 +27,7 @@ extern s8 FontMarginBottom;
extern const u8* font;
extern u8 *itemlist;
extern u8 flash_type;
extern u16 itemlist_offset;
extern u32 flash_sector_size;
extern u32 flash_itemlist_sector_offset;
extern u32 flash_status_sector_offset;
@@ -60,6 +61,7 @@ int main(void) {
u8 redraw_items = 0xFF;
u8 roms_page = 7;
u16 kHeld = 0;
u16 kHeld_boot = 0;
BOOL show_debug = FALSE;
BOOL show_credits = FALSE;
BOOL boot_failed = FALSE;
@@ -90,21 +92,31 @@ int main(void) {
SetMode(MODE_4 | BG2_ENABLE);
dmaCopy(bgBitmap, (void*)AGB_VRAM+0xA000, SCREEN_WIDTH * SCREEN_HEIGHT);
// Count number of ROMs
for (roms_total = 0; roms_total < 512; roms_total++) {
if ((itemlist[(0x70*roms_total+1)] == 0) || (itemlist[(0x70*roms_total+1)] == 0xFF)) break;
// Check on-boot keys
scanKeys();
kHeld = keysHeld();
kHeld_boot = kHeld;
if ((kHeld & KEY_SELECT) && (kHeld & KEY_R)) {
show_credits = TRUE;
} else if (kHeld & KEY_SELECT) {
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(); }
if (kHeld) {
BOOL found_keys = FALSE;
for (itemlist_offset = 0; itemlist_offset < 0xE000; 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) {
found_keys = TRUE;
break;
}
}
if (!found_keys) {
kHeld = 0;
itemlist_offset = 0;
}
}
page_total = (roms_total + 8.0 - 1) / 8.0;
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)) {
@@ -119,15 +131,29 @@ int main(void) {
page_active = sFlashStatus.last_boot_menu_index / 8;
}
// Check on-boot keys
scanKeys();
kHeld = keysHeld();
if ((kHeld & KEY_SELECT) && (kHeld & KEY_R)) {
show_credits = TRUE;
} else if (kHeld & KEY_SELECT) {
show_debug = TRUE;
// Count number of ROMs
for (roms_total = 0; roms_total < 512; roms_total++) {
memcpy(&sItemConfig, ((u8*)itemlist+itemlist_offset)+(0x70*roms_total), sizeof(sItemConfig));
if (sItemConfig.keys != kHeld) break;
if (sItemConfig.title_length == 0) break;
if (sItemConfig.title_length == 0xFF) break;
}
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+itemlist_offset), sizeof(sItemConfig));
u8 error_code = BootGame(sItemConfig, sFlashStatus);
boot_failed = error_code;
}
page_total = (roms_total + 8.0 - 1) / 8.0;
s32 wait = 0;
u8 f = 0;
while (1) {
@@ -143,9 +169,8 @@ int main(void) {
}
if (roms_page < 7) ClearList((void*)AGB_VRAM+0xA000, 26+(roms_page+1)*14, 14*(8-roms_page));
if (cursor_pos > roms_page) cursor_pos = roms_page;
for (u8 i = 0; i <= roms_page; i++) {
memcpy(&sItemConfig, ((u8*)itemlist)+0x70*(page_active*8+i), sizeof(sItemConfig));
memcpy(&sItemConfig, ((u8*)itemlist+itemlist_offset)+0x70*(page_active*8+i), sizeof(sItemConfig));
ClearList((void*)AGB_VRAM+0xA000, 27+i*14, 14);
LoadFont(sItemConfig.font);
DrawText(28, 26+i*14, ALIGN_LEFT, sItemConfig.title, sItemConfig.title_length, font, (void*)AGB_VRAM+0xA000, i == cursor_pos);
@@ -154,7 +179,7 @@ int main(void) {
// Re-draw only changed list items (cursor moved up or down)
for (u8 i = 0; i < 8; i++) {
if ((redraw_items >> i) & 1) {
memcpy(&sItemConfig, ((u8*)itemlist)+0x70*(page_active*8+i), sizeof(sItemConfig));
memcpy(&sItemConfig, ((u8*)itemlist+itemlist_offset)+0x70*(page_active*8+i), sizeof(sItemConfig));
ClearList((void*)AGB_VRAM+0xA000, 27+i*14, 14);
LoadFont(sItemConfig.font);
DrawText(28, 26+i*14, ALIGN_LEFT, sItemConfig.title, sItemConfig.title_length, font, (void*)AGB_VRAM+0xA000, i == cursor_pos);
@@ -162,7 +187,7 @@ int main(void) {
}
}
memcpy(&sItemConfig, ((u8*)itemlist)+0x70*(page_active*8+cursor_pos), sizeof(sItemConfig));
memcpy(&sItemConfig, ((u8*)itemlist+itemlist_offset)+0x70*(page_active*8+cursor_pos), sizeof(sItemConfig));
// Draw cursor
LoadFont(1);
@@ -212,6 +237,11 @@ int main(void) {
// Check for menu keys
scanKeys();
kHeld = keysHeld();
if ((kHeld_boot == 0) || (kHeld != kHeld_boot)) {
kHeld_boot = 0;
} else {
kHeld = 0;
}
if (kHeld != 0) {
wait++;
} else {
@@ -233,13 +263,15 @@ int main(void) {
sFlashStatus.last_boot_menu_index = page_active * 8 + cursor_pos;
if (!show_credits && !show_debug) {
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;
dmaCopy((void*)AGB_VRAM+0xA000, (void*)AGB_VRAM, SCREEN_WIDTH * SCREEN_HEIGHT);
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);
boot_failed = error_code;
redraw_items = 0xFF;

View File

@@ -66,7 +66,7 @@ typedef struct ItemConfig_
u16 rom_size;
SAVE_TYPE save_type;
u8 save_index;
u16 index;
u16 keys;
u8 reserved[6];
u16 title[0x30];
} ItemConfig;