mirror of
https://github.com/lesserkuma/GBA_MultiMenu.git
synced 2026-08-24 01:24:23 -05:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1bf4927d6b | ||
|
|
a3c4a44893 | ||
|
|
cbe17ef7c5 | ||
|
|
8dc1d10a95 | ||
|
|
bcb9ae5d65 |
2
Makefile
2
Makefile
@@ -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
|
||||
|
||||
15
README.md
15
README.md
@@ -15,15 +15,19 @@ 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)
|
||||
|
||||
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": [
|
||||
@@ -73,13 +77,14 @@ If the cartridge has no battery installed, the ROMs must be patched for batteryl
|
||||
|
||||
## Compatibility
|
||||
Tested repro cartridges:
|
||||
- 100BS6600_48BALL_V4 with 6600M0U0BE
|
||||
- 100SOP with MSP55LV100S
|
||||
- 100BS6600_48BALL_V4 with 6600M0U0BE
|
||||
- SUN100S_MSP54_XXX_BGA48 with MSP54LV100
|
||||
|
||||
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
|
||||
|
||||
## Screenshots
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import sys, os, glob, json, math, re, struct, hashlib, argparse, datetime
|
||||
|
||||
# Configuration
|
||||
app_version = "0.3"
|
||||
app_version = "0.8"
|
||||
default_file = "LK_MULTIMENU_<CODE>.gba"
|
||||
|
||||
################################
|
||||
@@ -51,6 +51,12 @@ cartridge_types = [
|
||||
"sector_size":0x40000,
|
||||
"block_size":0x80000,
|
||||
},
|
||||
{
|
||||
"name":"MSP54LV100",
|
||||
"flash_size":0x8000000,
|
||||
"sector_size":0x20000,
|
||||
"block_size":0x80000,
|
||||
},
|
||||
]
|
||||
now = datetime.datetime.now()
|
||||
log = ""
|
||||
@@ -83,19 +89,27 @@ if not os.path.exists(args.config):
|
||||
games = []
|
||||
cartridge_type = 1
|
||||
battery_present = False
|
||||
min_rom_size = 0x400000
|
||||
for file in files:
|
||||
games.append({
|
||||
d = {
|
||||
"enabled": True,
|
||||
"file": os.path.split(file)[1],
|
||||
"title": os.path.splitext(os.path.split(file)[1])[0],
|
||||
"title_font": 1,
|
||||
"save_slot": save_slot,
|
||||
})
|
||||
}
|
||||
with open(file, "rb") as f:
|
||||
f.seek(0xAC)
|
||||
code = f.read(0x4)
|
||||
if code[:3] in (b"BPG", b"BPR"):
|
||||
d["map_256m"] = True
|
||||
games.append(d)
|
||||
save_slot += 1
|
||||
obj = {
|
||||
"cartridge": {
|
||||
"type": cartridge_type + 1,
|
||||
"battery_present": battery_present,
|
||||
"min_rom_size": min_rom_size,
|
||||
},
|
||||
"games": games,
|
||||
}
|
||||
@@ -118,6 +132,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"]
|
||||
@@ -166,6 +184,13 @@ for game in games:
|
||||
x = 0x80000
|
||||
while (x < size): x *= 2
|
||||
size = x
|
||||
if size < 0x400000:
|
||||
with open(f"roms/{game['file']}", "rb") as f:
|
||||
buffer = f.read()
|
||||
if b"Batteryless mod by Lesserkuma" in buffer:
|
||||
size = max(0x400000, min_rom_size)
|
||||
else:
|
||||
size = max(size, min_rom_size)
|
||||
game["index"] = index
|
||||
game["size"] = size
|
||||
if "title_font" in game:
|
||||
@@ -310,7 +335,7 @@ 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("")
|
||||
logp("Cartridge Type: {:d} ({:s} {:s})".format(cartridge_type, cartridge_types[cartridge_type]["name"], "with battery" if battery_present else "without battery"))
|
||||
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)
|
||||
@@ -320,7 +345,7 @@ if args.split:
|
||||
size = 0x2000000
|
||||
if pos > len(compilation[:rom_size]): break
|
||||
if pos + size > rom_size: size = rom_size - pos
|
||||
output_file_part = "{:s}_part{:d}{:s}".format(os.path.splitext(output_file)[0], i + 1, os.path.splitext(output_file)[1])
|
||||
output_file_part = "{:s}_part{:d}{:s}".format(os.path.splitext(output_file)[0], i, os.path.splitext(output_file)[1])
|
||||
with open(output_file_part, "wb") as f: f.write(compilation[pos:pos+size])
|
||||
else:
|
||||
with open(output_file, "wb") as f: f.write(compilation[:rom_size])
|
||||
|
||||
@@ -66,6 +66,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 +135,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 +225,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;
|
||||
}
|
||||
@@ -215,14 +276,11 @@ IWRAM_CODE u8 BootGame(ItemConfig config, FlashStatus status)
|
||||
if (_flash_type == 0)
|
||||
return 1;
|
||||
|
||||
// Temporarily store SRAM values at mapper registers
|
||||
if (status.last_boot_save_type == SRAM_NONE)
|
||||
{
|
||||
sram_register_backup[0] = *(vu8 *)MAPPER_CONFIG1;
|
||||
sram_register_backup[1] = *(vu8 *)MAPPER_CONFIG2;
|
||||
sram_register_backup[2] = *(vu8 *)MAPPER_CONFIG3;
|
||||
sram_register_backup[3] = *(vu8 *)MAPPER_CONFIG4;
|
||||
}
|
||||
// Temporarily store SRAM values located at mapper registers
|
||||
sram_register_backup[0] = *(vu8 *)MAPPER_CONFIG1;
|
||||
sram_register_backup[1] = *(vu8 *)MAPPER_CONFIG2;
|
||||
sram_register_backup[2] = *(vu8 *)MAPPER_CONFIG3;
|
||||
sram_register_backup[3] = *(vu8 *)MAPPER_CONFIG4;
|
||||
|
||||
// Enable SRAM access
|
||||
*(vu8 *)MAPPER_CONFIG4 = 1;
|
||||
|
||||
@@ -15,7 +15,6 @@ Author: Lesserkuma (github.com/lesserkuma)
|
||||
}
|
||||
|
||||
#define MAGIC_FLASH_STATUS 0x414D554B
|
||||
#define FLASH_STATUS_LOCATION 0xA0000
|
||||
|
||||
typedef struct __attribute__((packed)) FlashStatus_
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user