mirror of
https://github.com/lesserkuma/GBA_MultiMenu.git
synced 2026-08-23 17:14:10 -05:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1bf4927d6b | ||
|
|
a3c4a44893 | ||
|
|
cbe17ef7c5 | ||
|
|
8dc1d10a95 | ||
|
|
bcb9ae5d65 | ||
|
|
a33f0ed9db | ||
|
|
34b1b4b8dc | ||
|
|
e21ad43d20 | ||
|
|
8f63cdb03f | ||
|
|
9499327b3b | ||
|
|
2c88286a66 | ||
|
|
ce1212d365 | ||
|
|
8cd4c1b953 | ||
|
|
d8df1ff28e | ||
|
|
b43c23db22 |
BIN
.github/screen.png
vendored
Normal file
BIN
.github/screen.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 360 KiB |
3
Makefile
3
Makefile
@@ -179,10 +179,13 @@ soundbank.bin soundbank.h : $(AUDIOFILES)
|
|||||||
|
|
||||||
#replacement rule for gbafix
|
#replacement rule for gbafix
|
||||||
#---------------------------------------------------------------------------------
|
#---------------------------------------------------------------------------------
|
||||||
|
$(shell touch $(CURDIR)/../$(SOURCES)/version.h)
|
||||||
%.gba: %.elf
|
%.gba: %.elf
|
||||||
@$(OBJCOPY) -O binary $< $@
|
@$(OBJCOPY) -O binary $< $@
|
||||||
@gbafix $@ "-tLK MULTIMENU" "-cAGBJ" "-mLK" "-r0"
|
@gbafix $@ "-tLK MULTIMENU" "-cAGBJ" "-mLK" "-r0"
|
||||||
|
@echo Copying to ROM builder folder
|
||||||
@cp $@ "$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))/rom_builder/lk_multimenu.gba"
|
@cp $@ "$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))/rom_builder/lk_multimenu.gba"
|
||||||
|
@echo Done!
|
||||||
|
|
||||||
#---------------------------------------------------------------------------------------
|
#---------------------------------------------------------------------------------------
|
||||||
endif
|
endif
|
||||||
|
|||||||
39
README.md
39
README.md
@@ -13,18 +13,32 @@ Open the config.json file in a text editor like Notepad.
|
|||||||
|
|
||||||
The following section must be edited in order to specify the cartridge type to use and whether or not your cartridge has a battery installed:
|
The following section must be edited in order to specify the cartridge type to use and whether or not your cartridge has a battery installed:
|
||||||
```json
|
```json
|
||||||
"cartridge": {
|
"cartridge": {
|
||||||
"type": 2,
|
"type": 2,
|
||||||
"battery_present": false
|
"battery_present": false,
|
||||||
},
|
"min_rom_size": 4194304
|
||||||
|
},
|
||||||
```
|
```
|
||||||
Set `type` to `1` or `2`:
|
Set `type` to `1` or `2`:
|
||||||
- `1` = MSP55LV100S (e.g. The Legend of Zelda Collection - Classic Edition 7-in-1)
|
- `1` = MSP55LV100S (e.g. The Legend of Zelda Collection - Classic Edition 7-in-1, 64 MiB)
|
||||||
- `2` = 6600M0U0BE (e.g. 369IN1 2048M)
|
- `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 `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:
|
In the `games` section, you can edit the game-related stuff:
|
||||||
|
```json
|
||||||
|
"games": [
|
||||||
|
{
|
||||||
|
"enabled": true,
|
||||||
|
"file": "wah7.gba",
|
||||||
|
"title": "Super WAHluigi Bros. 7",
|
||||||
|
"title_font": 1,
|
||||||
|
"save_slot": 1
|
||||||
|
},
|
||||||
|
```
|
||||||
- `enabled` can be set to `true` or `false`. If this option is set, the game entry will be skipped by the ROM Builder.
|
- `enabled` can be set to `true` or `false`. If this option is set, the game entry will be skipped by the ROM Builder.
|
||||||
- `file` is the ROM's file name within the **roms** folder, including file extension.
|
- `file` is the ROM's file name within the **roms** folder, including file extension.
|
||||||
- `title` is the unicode title that will be displayed in the menu.
|
- `title` is the unicode title that will be displayed in the menu.
|
||||||
@@ -36,6 +50,7 @@ In the `games` section, you can edit the game-related stuff:
|
|||||||
- `5` = Nintendo DSi IPL font (KOR)
|
- `5` = Nintendo DSi IPL font (KOR)
|
||||||
- `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.
|
||||||
|
|
||||||
### ROM Builder Command Line Arguments
|
### ROM Builder Command Line Arguments
|
||||||
|
|
||||||
@@ -62,7 +77,15 @@ If the cartridge has no battery installed, the ROMs must be patched for batteryl
|
|||||||
|
|
||||||
## Compatibility
|
## Compatibility
|
||||||
Tested repro cartridges:
|
Tested repro cartridges:
|
||||||
- 100BS6600_48BALL_V4 with 6600M0U0BE
|
|
||||||
- 100SOP with MSP55LV100S
|
- 100SOP with MSP55LV100S
|
||||||
|
- 100BS6600_48BALL_V4 with 6600M0U0BE
|
||||||
|
- SUN100S_MSP54_XXX_BGA48 with MSP54LV100
|
||||||
|
|
||||||
ROM and save data 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.
|
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, Jenetrix
|
||||||
|
|
||||||
|
## Screenshots
|
||||||
|
|
||||||
|
<img src="https://raw.githubusercontent.com/lesserkuma/GBA_MultiMenu/master/.github/screen.png" alt="" />
|
||||||
|
|||||||
@@ -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.1"
|
app_version = "0.8"
|
||||||
default_file = "LK_MULTIMENU_<CODE>.gba"
|
default_file = "LK_MULTIMENU_<CODE>.gba"
|
||||||
|
|
||||||
################################
|
################################
|
||||||
@@ -39,16 +39,24 @@ def logp(*args, **kwargs):
|
|||||||
################################
|
################################
|
||||||
|
|
||||||
cartridge_types = [
|
cartridge_types = [
|
||||||
{ # "MSP55LV100S
|
{
|
||||||
|
"name":"MSP55LV100S",
|
||||||
"flash_size":0x4000000,
|
"flash_size":0x4000000,
|
||||||
"sector_size":0x20000,
|
"sector_size":0x20000,
|
||||||
"block_size":0x80000,
|
"block_size":0x80000,
|
||||||
},
|
},
|
||||||
{ # 6600M0U0BE
|
{
|
||||||
|
"name":"6600M0U0BE",
|
||||||
"flash_size":0x10000000,
|
"flash_size":0x10000000,
|
||||||
"sector_size":0x40000,
|
"sector_size":0x40000,
|
||||||
"block_size":0x80000,
|
"block_size":0x80000,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name":"MSP54LV100",
|
||||||
|
"flash_size":0x8000000,
|
||||||
|
"sector_size":0x20000,
|
||||||
|
"block_size":0x80000,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
now = datetime.datetime.now()
|
now = datetime.datetime.now()
|
||||||
log = ""
|
log = ""
|
||||||
@@ -64,11 +72,11 @@ parser.add_argument("--output", type=str, default=default_file, help="sets the f
|
|||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
output_file = args.output
|
output_file = args.output
|
||||||
if output_file == "lk_multimenu.gba":
|
if output_file == "lk_multimenu.gba":
|
||||||
logp("Error: The file must not be named lk_multimenu.gba")
|
logp("Error: The file must not be named “lk_multimenu.gba”")
|
||||||
if not args.no_wait: input("\nPress ENTER to exit.\n")
|
if not args.no_wait: input("\nPress ENTER to exit.\n")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
if not os.path.exists("lk_multimenu.gba"):
|
if not os.path.exists("lk_multimenu.gba"):
|
||||||
logp("Error: The Menu ROM is missing.\nPlease put it in the same directory that you are running this tool from.\nExpected file name: \"lk_multimenu.gba\"")
|
logp("Error: The Menu ROM is missing.\nPlease put it in the same directory that you are running this tool from.\nExpected file name: “lk_multimenu.gba”")
|
||||||
if not args.no_wait: input("\nPress ENTER to exit.\n")
|
if not args.no_wait: input("\nPress ENTER to exit.\n")
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
@@ -81,30 +89,53 @@ if not os.path.exists(args.config):
|
|||||||
games = []
|
games = []
|
||||||
cartridge_type = 1
|
cartridge_type = 1
|
||||||
battery_present = False
|
battery_present = False
|
||||||
|
min_rom_size = 0x400000
|
||||||
for file in files:
|
for file in files:
|
||||||
games.append({
|
d = {
|
||||||
"enabled": True,
|
"enabled": True,
|
||||||
"file": os.path.split(file)[1],
|
"file": os.path.split(file)[1],
|
||||||
"title": os.path.splitext(os.path.split(file)[1])[0],
|
"title": os.path.splitext(os.path.split(file)[1])[0],
|
||||||
"title_font": 1,
|
"title_font": 1,
|
||||||
"save_slot": save_slot,
|
"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
|
save_slot += 1
|
||||||
obj = {
|
obj = {
|
||||||
"cartridge": {
|
"cartridge": {
|
||||||
"type": cartridge_type + 1,
|
"type": cartridge_type + 1,
|
||||||
"battery_present": battery_present,
|
"battery_present": battery_present,
|
||||||
|
"min_rom_size": min_rom_size,
|
||||||
},
|
},
|
||||||
"games": games,
|
"games": games,
|
||||||
}
|
}
|
||||||
with open(args.config, "w", encoding="UTF-8-SIG") as f:
|
if len(games) == 0:
|
||||||
f.write(json.dumps(obj=obj, indent=4, ensure_ascii=False))
|
logp("Error: No usable ROM files were found in the “roms” folder.")
|
||||||
|
else:
|
||||||
|
with open(args.config, "w", encoding="UTF-8-SIG") as f:
|
||||||
|
f.write(json.dumps(obj=obj, indent=4, ensure_ascii=False))
|
||||||
|
logp(f"A new configuration file ({args.config:s}) was created based on the files inside the “roms” folder.\nPlease edit the file to your liking in a text editor, then run this tool again.")
|
||||||
|
if not args.no_wait: input("\nPress ENTER to exit.\n")
|
||||||
|
sys.exit()
|
||||||
else:
|
else:
|
||||||
with open(args.config, "r", encoding="UTF-8-SIG") as f:
|
with open(args.config, "r", encoding="UTF-8-SIG") as f:
|
||||||
j = json.load(f)
|
try:
|
||||||
|
j = json.load(f)
|
||||||
|
except json.decoder.JSONDecodeError as e:
|
||||||
|
logp(f"Error: The configuration file ({args.config:s}) is malformed and could not be loaded.\n" + str(e))
|
||||||
|
if not args.no_wait: input("\nPress ENTER to exit.\n")
|
||||||
|
sys.exit()
|
||||||
games = j["games"]
|
games = j["games"]
|
||||||
cartridge_type = j["cartridge"]["type"] - 1
|
cartridge_type = j["cartridge"]["type"] - 1
|
||||||
battery_present = j["cartridge"]["battery_present"]
|
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
|
# Prepare compilation
|
||||||
flash_size = cartridge_types[cartridge_type]["flash_size"]
|
flash_size = cartridge_types[cartridge_type]["flash_size"]
|
||||||
@@ -128,15 +159,16 @@ UpdateSectorMap(start=0, length=math.ceil(len(menu_rom) / sector_size), c="m")
|
|||||||
item_list_offset = len(menu_rom)
|
item_list_offset = len(menu_rom)
|
||||||
item_list_offset = 0x40000 - (item_list_offset % 0x40000) + item_list_offset
|
item_list_offset = 0x40000 - (item_list_offset % 0x40000) + item_list_offset
|
||||||
item_list_offset = math.ceil(item_list_offset / sector_size)
|
item_list_offset = math.ceil(item_list_offset / sector_size)
|
||||||
UpdateSectorMap(start=item_list_offset, length=1, c="i")
|
UpdateSectorMap(start=item_list_offset, length=1, c="l")
|
||||||
status_offset = item_list_offset + 1
|
status_offset = item_list_offset + 1
|
||||||
UpdateSectorMap(start=status_offset, length=1, c="c")
|
UpdateSectorMap(start=status_offset, length=1, c="c")
|
||||||
if battery_present:
|
if battery_present:
|
||||||
status = bytearray([0x4B, 0x55, 0x4D, 0x41, 0x00, 0x01, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
|
status = bytearray([0x4B, 0x55, 0x4D, 0x41, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
|
||||||
else:
|
else:
|
||||||
status = bytearray([0x4B, 0x55, 0x4D, 0x41, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
|
status = bytearray([0x4B, 0x55, 0x4D, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
|
||||||
compilation[status_offset * sector_size:status_offset * sector_size + len(status)] = status
|
compilation[status_offset * sector_size:status_offset * sector_size + len(status)] = status
|
||||||
save_data_sector_offset = status_offset + 1
|
save_data_sector_offset = status_offset + 1
|
||||||
|
boot_logo_found = hashlib.sha1(compilation[0x04:0xA0]).digest() == bytearray([ 0x17, 0xDA, 0xA0, 0xFE, 0xC0, 0x2F, 0xC3, 0x3C, 0x0F, 0x6A, 0xBB, 0x54, 0x9A, 0x8B, 0x80, 0xB6, 0x61, 0x3B, 0x48, 0xEE ])
|
||||||
|
|
||||||
# Read game ROMs and import save data
|
# Read game ROMs and import save data
|
||||||
saves_read = []
|
saves_read = []
|
||||||
@@ -152,6 +184,13 @@ for game in games:
|
|||||||
x = 0x80000
|
x = 0x80000
|
||||||
while (x < size): x *= 2
|
while (x < size): x *= 2
|
||||||
size = x
|
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["index"] = index
|
||||||
game["size"] = size
|
game["size"] = size
|
||||||
if "title_font" in game:
|
if "title_font" in game:
|
||||||
@@ -190,7 +229,7 @@ else:
|
|||||||
|
|
||||||
games = [game for game in games if not ("missing" in game and game["missing"])]
|
games = [game for game in games if not ("missing" in game and game["missing"])]
|
||||||
if len(games) == 0:
|
if len(games) == 0:
|
||||||
logp("No ROMs found")
|
logp(f"No ROMs found. Delete the “{args.config:s}” file to reset your configuration.")
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
# Add index
|
# Add index
|
||||||
@@ -201,19 +240,24 @@ 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)
|
||||||
boot_logo_found = False
|
|
||||||
c = 0
|
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)):
|
||||||
if i % game["sector_count"] == 0:
|
sector_count_map = game["sector_count"]
|
||||||
|
|
||||||
|
if "map_256m" in game and game["map_256m"] == True:
|
||||||
|
# Map as 256M ROM, but don't waste space; some games may need this for unknown reasons
|
||||||
|
sector_count_map = (32 * 1024 * 1024) // sector_size
|
||||||
|
|
||||||
|
if i % sector_count_map == 0:
|
||||||
if sector_map[i:i + game["sector_count"]] == ["."] * game["sector_count"]:
|
if sector_map[i:i + game["sector_count"]] == ["."] * game["sector_count"]:
|
||||||
UpdateSectorMap(i, game["sector_count"], "r")
|
UpdateSectorMap(i, game["sector_count"], "r")
|
||||||
with open(f"roms/{game['file']}", "rb") as f: rom = f.read()
|
with open(f"roms/{game['file']}", "rb") as f: rom = f.read()
|
||||||
compilation[i * sector_size:i * sector_size + len(rom)] = rom
|
compilation[i * sector_size:i * sector_size + len(rom)] = rom
|
||||||
game["sector_offset"] = i
|
game["sector_offset"] = i
|
||||||
game["block_offset"] = game["sector_offset"] * sector_size // block_size
|
game["block_offset"] = game["sector_offset"] * sector_size // block_size
|
||||||
game["block_count"] = game["sector_count"] * sector_size // block_size
|
game["block_count"] = sector_count_map * sector_size // block_size
|
||||||
found = True
|
found = True
|
||||||
|
|
||||||
if not boot_logo_found and hashlib.sha1(rom[0x04:0xA0]).digest() == bytearray([ 0x17, 0xDA, 0xA0, 0xFE, 0xC0, 0x2F, 0xC3, 0x3C, 0x0F, 0x6A, 0xBB, 0x54, 0x9A, 0x8B, 0x80, 0xB6, 0x61, 0x3B, 0x48, 0xEE ]):
|
if not boot_logo_found and hashlib.sha1(rom[0x04:0xA0]).digest() == bytearray([ 0x17, 0xDA, 0xA0, 0xFE, 0xC0, 0x2F, 0xC3, 0x3C, 0x0F, 0x6A, 0xBB, 0x54, 0x9A, 0x8B, 0x80, 0xB6, 0x61, 0x3B, 0x48, 0xEE ]):
|
||||||
@@ -240,10 +284,10 @@ logp("{:.2f}% ({:d} of {:d} sectors) used\n".format(sectors_used / sector_count
|
|||||||
logp(f"Added {len(games)} ROM(s) to the compilation\n")
|
logp(f"Added {len(games)} ROM(s) to the compilation\n")
|
||||||
|
|
||||||
if battery_present:
|
if battery_present:
|
||||||
logp (" | Offset | Size | Save Slot | Title")
|
logp (" | Offset | Map Size | Save Slot | Title")
|
||||||
toc_sep = "----+-----------+-----------+----------------+---------------------------------"
|
toc_sep = "----+-----------+-----------+----------------+---------------------------------"
|
||||||
else:
|
else:
|
||||||
logp (" | Offset | Size | Title")
|
logp (" | Offset | Map Size | Title")
|
||||||
toc_sep = "----+-----------+-----------+--------------------------------------------------"
|
toc_sep = "----+-----------+-----------+--------------------------------------------------"
|
||||||
|
|
||||||
item_list = bytearray()
|
item_list = bytearray()
|
||||||
@@ -259,12 +303,11 @@ for game in games:
|
|||||||
if game['save_type'] > 0:
|
if game['save_type'] > 0:
|
||||||
table_line += f"{game['save_slot']+1:2d} (0x{(save_data_sector_offset + game['save_slot']) * sector_size:07X}) | "
|
table_line += f"{game['save_slot']+1:2d} (0x{(save_data_sector_offset + game['save_slot']) * sector_size:07X}) | "
|
||||||
else:
|
else:
|
||||||
table_line += "-------------- | "
|
table_line += " | "
|
||||||
table_line += f"{title}"
|
table_line += f"{title}"
|
||||||
if c % 8 == 0: logp(toc_sep)
|
if c % 8 == 0: logp(toc_sep)
|
||||||
logp(table_line)
|
logp(table_line)
|
||||||
c += 1
|
c += 1
|
||||||
#logp(f"0x{game['block_offset'] * block_size:07X} |")
|
|
||||||
|
|
||||||
title = title.ljust(0x30, "\0")
|
title = title.ljust(0x30, "\0")
|
||||||
item_list += bytearray(struct.pack("B", game["title_font"]))
|
item_list += bytearray(struct.pack("B", game["title_font"]))
|
||||||
@@ -275,6 +318,7 @@ for game in games:
|
|||||||
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([0] * 8)
|
||||||
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
|
||||||
rom_code = "L{:s}".format(hashlib.sha1(status + item_list).hexdigest()[:3]).upper()
|
rom_code = "L{:s}".format(hashlib.sha1(status + item_list).hexdigest()[:3]).upper()
|
||||||
|
|
||||||
@@ -287,8 +331,13 @@ for i in range(0xA0, 0xBD):
|
|||||||
checksum = (checksum - 0x19) & 0xFF
|
checksum = (checksum - 0x19) & 0xFF
|
||||||
compilation[0xBD] = checksum
|
compilation[0xBD] = checksum
|
||||||
logp("")
|
logp("")
|
||||||
logp("Compilation ROM size: {:.2f} MiB".format(rom_size / 1024 / 1024))
|
logp("Menu ROM: 0x{:07X}–0x{:07X}".format(0, len(menu_rom)))
|
||||||
logp("ROM code: {:s}".format(rom_code))
|
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 + 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)
|
output_file = output_file.replace("<CODE>", rom_code)
|
||||||
if args.split:
|
if args.split:
|
||||||
for i in range(0, math.ceil(flash_size / 0x2000000)):
|
for i in range(0, math.ceil(flash_size / 0x2000000)):
|
||||||
@@ -296,7 +345,7 @@ if args.split:
|
|||||||
size = 0x2000000
|
size = 0x2000000
|
||||||
if pos > len(compilation[:rom_size]): break
|
if pos > len(compilation[:rom_size]): break
|
||||||
if pos + size > rom_size: size = rom_size - pos
|
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])
|
with open(output_file_part, "wb") as f: f.write(compilation[pos:pos+size])
|
||||||
else:
|
else:
|
||||||
with open(output_file, "wb") as f: f.write(compilation[:rom_size])
|
with open(output_file, "wb") as f: f.write(compilation[:rom_size])
|
||||||
@@ -307,7 +356,3 @@ if not args.no_log:
|
|||||||
log += "\n################################\n\n"
|
log += "\n################################\n\n"
|
||||||
with open("log.txt", "ab") as f: f.write(log.encode("UTF-8-SIG"))
|
with open("log.txt", "ab") as f: f.write(log.encode("UTF-8-SIG"))
|
||||||
if not args.no_wait: input("\nPress ENTER to exit.\n")
|
if not args.no_wait: input("\nPress ENTER to exit.\n")
|
||||||
|
|
||||||
# Debug
|
|
||||||
#with open("output_menu.gba", "wb") as f: f.write(compilation[:save_data_sector_offset * sector_size])
|
|
||||||
#with open("output_menu+save.gba", "wb") as f: f.write(compilation[:save_end_offset * sector_size])
|
|
||||||
|
|||||||
@@ -66,6 +66,22 @@ IWRAM_CODE void FlashDetectType(void)
|
|||||||
return;
|
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
|
// Unknown type
|
||||||
REG_IE = ie;
|
REG_IE = ie;
|
||||||
flash_type = 0;
|
flash_type = 0;
|
||||||
@@ -119,6 +135,24 @@ IWRAM_CODE void FlashEraseSector(u32 address)
|
|||||||
}
|
}
|
||||||
_FLASH_WRITE(address, 0xF0F0);
|
_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;
|
REG_IE = ie;
|
||||||
}
|
}
|
||||||
@@ -191,6 +225,33 @@ IWRAM_CODE void FlashWriteData(u32 address, u32 length)
|
|||||||
}
|
}
|
||||||
_FLASH_WRITE(address, 0xF0F0);
|
_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;
|
REG_IE = ie;
|
||||||
}
|
}
|
||||||
@@ -215,7 +276,7 @@ IWRAM_CODE u8 BootGame(ItemConfig config, FlashStatus status)
|
|||||||
if (_flash_type == 0)
|
if (_flash_type == 0)
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
// Temporarily store SRAM values at mapper registers
|
// Temporarily store SRAM values located at mapper registers
|
||||||
sram_register_backup[0] = *(vu8 *)MAPPER_CONFIG1;
|
sram_register_backup[0] = *(vu8 *)MAPPER_CONFIG1;
|
||||||
sram_register_backup[1] = *(vu8 *)MAPPER_CONFIG2;
|
sram_register_backup[1] = *(vu8 *)MAPPER_CONFIG2;
|
||||||
sram_register_backup[2] = *(vu8 *)MAPPER_CONFIG3;
|
sram_register_backup[2] = *(vu8 *)MAPPER_CONFIG3;
|
||||||
@@ -225,7 +286,8 @@ IWRAM_CODE u8 BootGame(ItemConfig config, FlashStatus status)
|
|||||||
*(vu8 *)MAPPER_CONFIG4 = 1;
|
*(vu8 *)MAPPER_CONFIG4 = 1;
|
||||||
|
|
||||||
// Write previous SRAM to flash
|
// Write previous SRAM to flash
|
||||||
if (status.battery_present) {
|
if (status.battery_present)
|
||||||
|
{
|
||||||
if (status.last_boot_save_type != SRAM_NONE)
|
if (status.last_boot_save_type != SRAM_NONE)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < SRAM_SIZE; i++)
|
for (int i = 0; i < SRAM_SIZE; i++)
|
||||||
@@ -279,7 +341,7 @@ IWRAM_CODE u8 BootGame(ItemConfig config, FlashStatus status)
|
|||||||
*(vu8 *)MAPPER_CONFIG3 = 0x40 - config.rom_size; // accessible ROM size (in 512 KB blocks)
|
*(vu8 *)MAPPER_CONFIG3 = 0x40 - config.rom_size; // accessible ROM size (in 512 KB blocks)
|
||||||
|
|
||||||
// Wait until menu ROM is no longer visible
|
// Wait until menu ROM is no longer visible
|
||||||
u16 timeout = 0xFFFF;
|
u32 timeout = 0x2FFF;
|
||||||
while (((vu16 *)AGB_ROM)[0x58] == 0x4B4C)
|
while (((vu16 *)AGB_ROM)[0x58] == 0x4B4C)
|
||||||
{
|
{
|
||||||
if (!timeout--)
|
if (!timeout--)
|
||||||
@@ -294,16 +356,20 @@ IWRAM_CODE u8 BootGame(ItemConfig config, FlashStatus status)
|
|||||||
// Lock mapper
|
// Lock mapper
|
||||||
*(vu8 *)MAPPER_CONFIG2 |= 0x80;
|
*(vu8 *)MAPPER_CONFIG2 |= 0x80;
|
||||||
|
|
||||||
// Restore SRAM values at mapper registers
|
|
||||||
*(vu8 *)MAPPER_CONFIG1 = sram_register_backup[0];
|
|
||||||
*(vu8 *)MAPPER_CONFIG2 = sram_register_backup[1];
|
|
||||||
*(vu8 *)MAPPER_CONFIG3 = sram_register_backup[2];
|
|
||||||
*(vu8 *)MAPPER_CONFIG4 = sram_register_backup[3];
|
|
||||||
|
|
||||||
// Write buffer to SRAM
|
// Write buffer to SRAM
|
||||||
for (int i = 0; i < SRAM_SIZE; i++)
|
if (config.save_type != SRAM_NONE)
|
||||||
{
|
{
|
||||||
((vu8 *)AGB_SRAM)[i] = data_buffer[i];
|
for (int i = 0; i < SRAM_SIZE; i++)
|
||||||
|
{
|
||||||
|
((vu8 *)AGB_SRAM)[i] = data_buffer[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
*(vu8 *)MAPPER_CONFIG1 = sram_register_backup[0];
|
||||||
|
*(vu8 *)MAPPER_CONFIG2 = sram_register_backup[1];
|
||||||
|
*(vu8 *)MAPPER_CONFIG3 = sram_register_backup[2];
|
||||||
|
*(vu8 *)MAPPER_CONFIG4 = sram_register_backup[3];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear palette
|
// Clear palette
|
||||||
@@ -313,7 +379,7 @@ IWRAM_CODE u8 BootGame(ItemConfig config, FlashStatus status)
|
|||||||
}
|
}
|
||||||
REG_BLDY = 0;
|
REG_BLDY = 0;
|
||||||
|
|
||||||
// Soft Reset system call
|
// Boot ROM
|
||||||
__asm("swi 0"); // Soft reset
|
__asm("swi 0"); // Soft reset
|
||||||
|
|
||||||
return 3;
|
return 3;
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ Author: Lesserkuma (github.com/lesserkuma)
|
|||||||
}
|
}
|
||||||
|
|
||||||
#define MAGIC_FLASH_STATUS 0x414D554B
|
#define MAGIC_FLASH_STATUS 0x414D554B
|
||||||
#define FLASH_STATUS_LOCATION 0xA0000
|
|
||||||
|
|
||||||
typedef struct __attribute__((packed)) FlashStatus_
|
typedef struct __attribute__((packed)) FlashStatus_
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ Author: Lesserkuma (github.com/lesserkuma)
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "version.h"
|
||||||
#include "main.h"
|
#include "main.h"
|
||||||
#include "font.h"
|
#include "font.h"
|
||||||
#include "flash.h"
|
#include "flash.h"
|
||||||
@@ -106,7 +107,7 @@ int main(void) {
|
|||||||
page_total = (roms_total + 8.0 - 1) / 8.0;
|
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)) {
|
||||||
sFlashStatus.magic = MAGIC_FLASH_STATUS;
|
sFlashStatus.magic = MAGIC_FLASH_STATUS;
|
||||||
sFlashStatus.version = 0;
|
sFlashStatus.version = 0;
|
||||||
sFlashStatus.battery_present = 1;
|
sFlashStatus.battery_present = 1;
|
||||||
@@ -186,14 +187,10 @@ int main(void) {
|
|||||||
}
|
}
|
||||||
} else if (show_credits) {
|
} else if (show_credits) {
|
||||||
LoadFont(0);
|
LoadFont(0);
|
||||||
#ifdef __TIMESTAMP_ISO__
|
memset(temp_unicode, 0, sizeof(temp_unicode));
|
||||||
memset(temp_unicode, 0, sizeof(temp_unicode));
|
snprintf(temp_ascii, 48, "Menu by LK - %s", BUILDTIME);
|
||||||
snprintf(temp_ascii, 48, "Menu by LK - %s", __TIMESTAMP_ISO__);
|
AsciiToUnicode(temp_ascii, temp_unicode);
|
||||||
AsciiToUnicode(temp_ascii, temp_unicode);
|
DrawText(6, SCREEN_HEIGHT - sFontSpecs.max_height - 3 - FontMarginBottom, ALIGN_LEFT, temp_unicode, 48, font, (void*)AGB_VRAM+0xA000, FALSE);
|
||||||
DrawText(6, SCREEN_HEIGHT - sFontSpecs.max_height - 3 - FontMarginBottom, ALIGN_LEFT, temp_unicode, 48, font, (void*)AGB_VRAM+0xA000, FALSE);
|
|
||||||
#else
|
|
||||||
DrawText(6, SCREEN_HEIGHT - sFontSpecs.max_height - 3 - FontMarginBottom, ALIGN_LEFT, u"Menu by LK", 48, font, (void*)AGB_VRAM+0xA000, FALSE);
|
|
||||||
#endif
|
|
||||||
} else if (show_debug) {
|
} else if (show_debug) {
|
||||||
LoadFont(0);
|
LoadFont(0);
|
||||||
u8 a = ((sItemConfig.rom_offset / 0x40) & 0xF) << 4;
|
u8 a = ((sItemConfig.rom_offset / 0x40) & 0xF) << 4;
|
||||||
|
|||||||
15
source/version.h
Normal file
15
source/version.h
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
/*
|
||||||
|
GBA Multi Game Menu
|
||||||
|
Author: Lesserkuma (github.com/lesserkuma)
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef VERSION_H_
|
||||||
|
#define VERSION_H_
|
||||||
|
|
||||||
|
#ifdef __TIMESTAMP_ISO__
|
||||||
|
#define BUILDTIME __TIMESTAMP_ISO__
|
||||||
|
#else
|
||||||
|
#define BUILDTIME __TIMESTAMP__
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
Reference in New Issue
Block a user