This commit is contained in:
Lesserkuma 2023-07-26 15:26:19 +02:00
parent 49a9b072cc
commit 08cc7b6bc8
15 changed files with 2134 additions and 1 deletions

189
Makefile Normal file
View File

@ -0,0 +1,189 @@
#---------------------------------------------------------------------------------
.SUFFIXES:
#---------------------------------------------------------------------------------
ifeq ($(strip $(DEVKITARM)),)
$(error "Please set DEVKITARM in your environment. export DEVKITARM=<path to>devkitARM")
endif
include $(DEVKITARM)/gba_rules
#---------------------------------------------------------------------------------
# TARGET is the name of the output
# BUILD is the directory where object files & intermediate files will be placed
# SOURCES is a list of directories containing source code
# INCLUDES is a list of directories containing extra header files
# DATA is a list of directories containing binary data
# GRAPHICS is a list of directories containing files to be processed by grit
#
# All directories are specified relative to the project directory where
# the makefile is found
#
#---------------------------------------------------------------------------------
TARGET := $(notdir $(CURDIR))
BUILD := build
SOURCES := source
INCLUDES := include
DATA := fonts
GRAPHICS := graphics
MUSIC :=
#---------------------------------------------------------------------------------
# options for code generation
#---------------------------------------------------------------------------------
ARCH := -mthumb -mthumb-interwork
CFLAGS := -g -Wall -Os \
-mcpu=arm7tdmi -mtune=arm7tdmi \
-fomit-frame-pointer \
-ffast-math \
$(ARCH)
CFLAGS += -D__TIMESTAMP_ISO__=$(shell date -u +'"\"%Y-%m-%dT%H:%M:%SZ\""')
CFLAGS += $(INCLUDE)
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions
ASFLAGS := -g $(ARCH)
LDFLAGS = -g $(ARCH) -Wl,-Map,$(notdir $*.map)
#---------------------------------------------------------------------------------
# any extra libraries we wish to link with the project
#---------------------------------------------------------------------------------
LIBS := -lmm -lgba
#---------------------------------------------------------------------------------
# list of directories containing libraries, this must be the top level containing
# include and lib
#---------------------------------------------------------------------------------
LIBDIRS := $(LIBGBA)
#---------------------------------------------------------------------------------
# no real need to edit anything past this point unless you need to add additional
# rules for different file extensions
#---------------------------------------------------------------------------------
ifneq ($(BUILDDIR), $(CURDIR))
#---------------------------------------------------------------------------------
export OUTPUT := $(CURDIR)/$(TARGET)
export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \
$(foreach dir,$(DATA),$(CURDIR)/$(dir)) \
$(foreach dir,$(GRAPHICS),$(CURDIR)/$(dir))
export DEPSDIR := $(CURDIR)/$(BUILD)
CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c)))
CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp)))
SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s)))
BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*)))
GFXFILES := $(foreach dir,$(GRAPHICS),$(notdir $(wildcard $(dir)/*.png)))
ifneq ($(strip $(MUSIC)),)
export AUDIOFILES := $(foreach dir,$(notdir $(wildcard $(MUSIC)/*.*)),$(CURDIR)/$(MUSIC)/$(dir))
BINFILES += soundbank.bin
endif
#---------------------------------------------------------------------------------
# use CXX for linking C++ projects, CC for standard C
#---------------------------------------------------------------------------------
ifeq ($(strip $(CPPFILES)),)
#---------------------------------------------------------------------------------
export LD := $(CC)
#---------------------------------------------------------------------------------
else
#---------------------------------------------------------------------------------
export LD := $(CXX)
#---------------------------------------------------------------------------------
endif
#---------------------------------------------------------------------------------
export OFILES_BIN := $(addsuffix .o,$(BINFILES))
export OFILES_GRAPHICS := $(GFXFILES:.png=.o)
export OFILES_SOURCES := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o)
export OFILES := $(OFILES_BIN) $(OFILES_GRAPHICS) $(OFILES_SOURCES)
export HFILES := $(addsuffix .h,$(subst .,_,$(BINFILES)))
export INCLUDE := $(foreach dir,$(INCLUDES),-iquote $(CURDIR)/$(dir)) \
$(foreach dir,$(LIBDIRS),-I$(dir)/include) \
-I$(CURDIR)/$(BUILD)
export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib)
.PHONY: $(BUILD) clean
#---------------------------------------------------------------------------------
$(BUILD):
@[ -d $@ ] || mkdir -p $@
@$(MAKE) BUILDDIR=`cd $(BUILD) && pwd` --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile
#---------------------------------------------------------------------------------
clean:
@echo clean ...
@rm -fr $(BUILD) $(TARGET).elf $(TARGET).gba $(TARGET).sav
#---------------------------------------------------------------------------------
else
#---------------------------------------------------------------------------------
# main targets
#---------------------------------------------------------------------------------
$(OUTPUT).gba : $(OUTPUT).elf
$(OUTPUT).elf : $(OFILES)
$(OFILES_SOURCES) : $(HFILES)
#---------------------------------------------------------------------------------
# The bin2o rule should be copied and modified
# for each extension used in the data directories
#---------------------------------------------------------------------------------
#---------------------------------------------------------------------------------
# rule to build soundbank from music files
#---------------------------------------------------------------------------------
soundbank.bin soundbank.h : $(AUDIOFILES)
#---------------------------------------------------------------------------------
@mmutil $^ -osoundbank.bin -hsoundbank.h
#---------------------------------------------------------------------------------
# This rule links in binary data with the .bin extension
#---------------------------------------------------------------------------------
%.bin.o %_bin.h : %.bin
#---------------------------------------------------------------------------------
@echo $(notdir $<)
@$(bin2o)
#---------------------------------------------------------------------------------
%.nftr.o %_nftr.h : %.nftr
#---------------------------------------------------------------------------------
@echo $(notdir $<)
@$(bin2o)
#---------------------------------------------------------------------------------
%.s %.h : %.png %.grit
#---------------------------------------------------------------------------------
grit $< -fts -o$*
-include $(DEPSDIR)/*.d
#replacement rule for gbafix
#---------------------------------------------------------------------------------
%.gba: %.elf
@$(OBJCOPY) -O binary $< $@
@gbafix $@ "-tLK MULTIMENU" "-cAGBJ" "-mLK" "-r0"
@cp $@ "$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))/rom_builder/lk_multimenu.gba"
#---------------------------------------------------------------------------------------
endif
#---------------------------------------------------------------------------------------

View File

@ -1 +1,68 @@
# GBA_MultiMenu
# GBA Multi Game Menu (by Lesserkuma)
This is a menu program to be run on Game Boy Advance bootleg cartridges which are equipped with a special multi-game mapper.
The binaries are available in the [Releases](https://github.com/lesserkuma/GBA_MultiMenu/releases) section.
## Usage
Place your ROM files and save data files into the `roms` folder, then run the ROM Builder tool. Upon first launch, it will create a config.json automatically which you can then modify further to your liking. To reset the configuration and re-generate a new one, just delete the config.json file.
### Configuration
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:
```json
"cartridge": {
"type": 2,
"battery_present": false
},
```
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)
Set `battery_present` to `true` or `false`. This will enable enhanced save data handling which will only be functional with a working battery.
In the `games` section, you can edit the game-related stuff:
- `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.
- `title` is the unicode title that will be displayed in the menu.
- `title_font` is set to `1` by default. If you have certain non-free fonts installed, the following options can be made available:
- `1` = Default font (based on [Fusion Pixel](https://github.com/TakWolf/fusion-pixel-font))
- `2` = Nintendo DS IPL font
- `3` = Nintendo DSi IPL font (JPN/USA/EUR)
- `4` = Nintendo DSi IPL font (CHN)
- `5` = Nintendo DSi IPL font (KOR)
- `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.
### ROM Builder Command Line Arguments
No command line arguments are required for creating a compilation, however there are some optional ones that can tweak some things:
```
--split splits output files into 32 MiB parts
--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
--output output.gba sets the file name of the compilation ROM
```
## Limitations
- up to 512 ROMs total (depending on cartridge memory)
- smallest ROM size is 512 KiB
- up to 256 MiB combined file size (depending on cartridge memory; also since ROMs need to be aligned in a very specific way, there may be less usable space)
- up to 64 KiB of save data per ROM
### Save Data
If the cartridge has a battery installed, the ROMs must be SRAM-patched with [GBATA](https://www.romhacking.net/utilities/601/) for saving to work.
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/).
## Compatibility
Tested repro cartridges:
- 100BS6600_48BALL_V4 with 6600M0U0BE
- 100SOP with MSP55LV100S
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.

View File

@ -0,0 +1,398 @@
Copyright (c) 2022, TakWolf (https://takwolf.com),
with Reserved Font Name 'Fusion Pixel'.
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
####
Copyright (c) 2021, TakWolf (https://takwolf.com),
with Reserved Font Name 'Ark Pixel'.
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
####
[Cubic 11]
These fonts are free software.
Unlimited permission is granted to use, copy, and distribute them, with or without modification, either commercially or noncommercially.
THESE FONTS ARE PROVIDED "AS IS" WITHOUT WARRANTY.
此字型是免費的。
無論您是否進行對本字型進行商業或非商業性修改,均可無限制地使用,複製和分發它們。
本字型的衍生品之授權必須與此字型相同,且不作任何擔保。
[JF Dot M+H 12]
Copyright(c) 2005 M+ FONTS PROJECT
[M+ BITMAP FONTS]
Copyright (C) 2002-2004 COZ
These fonts are free software.
Unlimited permission is granted to use, copy, and distribute it, with or without modification, either commercially and noncommercially.
THESE FONTS ARE PROVIDED "AS IS" WITHOUT WARRANTY.
これらのフォントはフリー(自由な)ソフトウエアです。
あらゆる改変の有無に関わらず、また商業的な利用であっても、自由にご利用、複製、再配布することができますが、全て無保証とさせていただきます。
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
https://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
####
Copyright (c) 2019-2023 Minseo Lee (itoupluk427@gmail.com)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

Binary file not shown.

BIN
fonts/font.nftr Normal file

Binary file not shown.

5
graphics/bg.grit Normal file
View File

@ -0,0 +1,5 @@
# 8 bit bitmap
-gB8
# bitmap format
-gb

BIN
graphics/bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

313
rom_builder/rom_builder.py Normal file
View File

@ -0,0 +1,313 @@
# -*- coding: utf-8 -*-
# GBA Multi Game Menu ROM Builder
# Author: Lesserkuma (github.com/lesserkuma)
import sys, os, glob, json, math, re, struct, hashlib, argparse, datetime
# Configuration
app_version = "0.1"
default_file = "LK_MULTIMENU_<CODE>.gba"
################################
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)
elif size < 1024:
return "{:d} Bytes".format(size)
elif size < 1024 * 1024:
val = size/1024
return "{:.1f} KB".format(val)
else:
val = size/1024/1024
return "{:.2f} MB".format(val)
def logp(*args, **kwargs):
global log
s = format(" ".join(map(str, args)))
print("{:s}".format(s), **kwargs)
if "end" in kwargs and kwargs["end"] == "":
log += "{:s}".format(s)
else:
log += "{:s}\n".format(s)
################################
cartridge_types = [
{ # "MSP55LV100S
"flash_size":0x4000000,
"sector_size":0x20000,
"block_size":0x80000,
},
{ # 6600M0U0BE
"flash_size":0x10000000,
"sector_size":0x40000,
"block_size":0x80000,
},
]
now = datetime.datetime.now()
log = ""
logp("GBA Multi Game Menu ROM Builder v{:s}\nby Lesserkuma\n".format(app_version))
class ArgParseCustomFormatter(argparse.ArgumentDefaultsHelpFormatter, argparse.RawDescriptionHelpFormatter): pass
parser = argparse.ArgumentParser()
parser.add_argument("--split", help="splits output files into 32 MiB parts", action="store_true", default=False)
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("--output", type=str, default=default_file, help="sets the file name of the compilation ROM")
args = parser.parse_args()
output_file = args.output
if output_file == "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")
sys.exit(1)
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\"")
if not args.no_wait: input("\nPress ENTER to exit.\n")
sys.exit()
# Read game list
files = []
if not os.path.exists(args.config):
files = glob.glob("roms/*.gba")
files = sorted(files, key=str.casefold)
save_slot = 1
games = []
cartridge_type = 1
battery_present = False
for file in files:
games.append({
"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,
})
save_slot += 1
obj = {
"cartridge": {
"type": cartridge_type + 1,
"battery_present": battery_present,
},
"games": games,
}
with open(args.config, "w", encoding="UTF-8-SIG") as f:
f.write(json.dumps(obj=obj, indent=4, ensure_ascii=False))
else:
with open(args.config, "r", encoding="UTF-8-SIG") as f:
j = json.load(f)
games = j["games"]
cartridge_type = j["cartridge"]["type"] - 1
battery_present = j["cartridge"]["battery_present"]
# Prepare compilation
flash_size = cartridge_types[cartridge_type]["flash_size"]
sector_size = cartridge_types[cartridge_type]["sector_size"]
sector_count = flash_size // sector_size
block_size = cartridge_types[cartridge_type]["block_size"]
block_count = flash_size // block_size
sectors_per_block = 0x80000 // sector_size
compilation = bytearray()
for i in range(flash_size // 0x2000000):
chunk = bytearray([0xFF] * 0x2000000)
compilation += chunk
sector_map = list("." * sector_count)
# Read menu ROM
with open("lk_multimenu.gba", "rb") as f:
menu_rom = f.read()
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")
item_list_offset = len(menu_rom)
item_list_offset = 0x40000 - (item_list_offset % 0x40000) + item_list_offset
item_list_offset = math.ceil(item_list_offset / sector_size)
UpdateSectorMap(start=item_list_offset, length=1, c="i")
status_offset = item_list_offset + 1
UpdateSectorMap(start=status_offset, length=1, c="c")
if battery_present:
status = bytearray([0x4B, 0x55, 0x4D, 0x41, 0x00, 0x01, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
else:
status = bytearray([0x4B, 0x55, 0x4D, 0x41, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
compilation[status_offset * sector_size:status_offset * sector_size + len(status)] = status
save_data_sector_offset = status_offset + 1
# Read game ROMs and import save data
saves_read = []
games = [game for game in games if "enabled" in game and game["enabled"]]
index = 0
for game in games:
if not game["enabled"]: continue
if not os.path.exists(f"roms/{game['file']}"):
game["missing"] = True
continue
size = os.path.getsize(f"roms/{game['file']}")
if ((size & (size - 1)) != 0):
x = 0x80000
while (x < size): x *= 2
size = x
game["index"] = index
game["size"] = size
if "title_font" in game:
game["title_font"] -= 1
else:
game["title_font"] = 0
game["sector_count"] = int(size / sector_size)
if battery_present and game["save_slot"] is not None:
game["save_type"] = 2
game["save_slot"] -= 1
save_slot = game["save_slot"]
offset = save_data_sector_offset + save_slot
UpdateSectorMap(offset, 1, "s")
if save_slot not in saves_read:
save_data_file = os.path.splitext(f"roms/{game['file']}")[0] + ".sav"
save_data = bytearray([0] * sector_size)
if os.path.exists(save_data_file):
with open(save_data_file, "rb") as f:
save_data = f.read()
if len(save_data) < sector_size:
save_data += bytearray([0] * (sector_size - len(save_data)))
if len(save_data) > sector_size:
save_data = save_data[:sector_size]
saves_read.append(save_slot)
compilation[offset * sector_size:offset * sector_size + sector_size] = save_data
else:
game["save_type"] = 0
game["save_slot"] = 0
index += 1
if len(saves_read) > 0:
save_end_offset = (''.join(sector_map).rindex("S") + 1)
else:
save_end_offset = save_data_sector_offset
games = [game for game in games if not ("missing" in game and game["missing"])]
if len(games) == 0:
logp("No ROMs found")
sys.exit()
# Add index
index = 0
for game in games:
game["index"] = index
index += 1
# Read ROM data
games.sort(key=lambda game: game["size"], reverse=True)
boot_logo_found = False
c = 0
for game in games:
found = False
for i in range(save_end_offset, len(sector_map)):
if i % game["sector_count"] == 0:
if sector_map[i:i + game["sector_count"]] == ["."] * game["sector_count"]:
UpdateSectorMap(i, game["sector_count"], "r")
with open(f"roms/{game['file']}", "rb") as f: rom = f.read()
compilation[i * sector_size:i * sector_size + len(rom)] = rom
game["sector_offset"] = i
game["block_offset"] = game["sector_offset"] * sector_size // block_size
game["block_count"] = game["sector_count"] * sector_size // block_size
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 ]):
compilation[0x04:0xA0] = rom[0x04:0xA0] # boot logo
boot_logo_found = True
break
if not found:
logp("{:s}” couldnt be added because it exceeds the available cartridge space.".format(game["title"]))
if not boot_logo_found:
logp("Warning: Valid boot logo is missing!")
# Generate item list
games = [game for game in games if "sector_offset" in game]
games.sort(key=lambda game: game["index"])
# Print information
logp("Sector map (1 block = {:d} KiB):".format(sector_size // 1024))
for i in range(0, len(sector_map)):
logp(sector_map[i], end="")
if i % 64 == 63: logp("")
sectors_used = len(re.findall(r'[MmSsRrIiCc]', "".join(sector_map)))
logp("{:.2f}% ({:d} of {:d} sectors) used\n".format(sectors_used / sector_count * 100, sectors_used, sector_count))
logp(f"Added {len(games)} ROM(s) to the compilation\n")
if battery_present:
logp (" | Offset | Size | Save Slot | Title")
toc_sep = "----+-----------+-----------+----------------+---------------------------------"
else:
logp (" | Offset | 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
#logp(f"0x{game['block_offset'] * block_size:07X} |")
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"))
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()
# Write compilation
rom_size = len("".join(sector_map).rstrip(".")) * sector_size
compilation[0xAC:0xB0] = rom_code.encode("ASCII")
checksum = 0
for i in range(0xA0, 0xBD):
checksum = checksum - compilation[i]
checksum = (checksum - 0x19) & 0xFF
compilation[0xBD] = checksum
logp("")
logp("Compilation ROM size: {:.2f} MiB".format(rom_size / 1024 / 1024))
logp("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
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])
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])
# Write log
if not args.no_log:
log += "\nArgument List: {:s}\n".format(str(sys.argv[1:]))
log += "\n################################\n\n"
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")
# 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])

View File

320
source/flash.c Normal file
View File

@ -0,0 +1,320 @@
/*
GBA Multi Game Menu
Author: Lesserkuma (github.com/lesserkuma)
*/
#include <gba.h>
#include <stdio.h>
#include <string.h>
#include "main.h"
#include "flash.h"
u8 flash_type;
u8 *itemlist;
u32 flash_sector_size;
u32 flash_itemlist_sector_offset;
u32 flash_status_sector_offset;
u32 flash_save_sector_offset;
EWRAM_BSS u8 sram_register_backup[4];
EWRAM_BSS u8 data_buffer[SRAM_SIZE];
void FlashCalcOffsets(void)
{
u32 own_size = (u32)(&__rom_end__) - 0x8000000;
flash_itemlist_sector_offset = own_size;
flash_itemlist_sector_offset = 0x40000 - (flash_itemlist_sector_offset % 0x40000) + flash_itemlist_sector_offset;
flash_itemlist_sector_offset = _DIV_CEIL(flash_itemlist_sector_offset, flash_sector_size);
flash_status_sector_offset = flash_itemlist_sector_offset + 1;
flash_save_sector_offset = flash_status_sector_offset + 1;
itemlist = (u8 *)(AGB_ROM + flash_itemlist_sector_offset * flash_sector_size);
}
IWRAM_CODE void FlashDetectType(void)
{
u32 data;
u16 ie = REG_IE;
REG_IE = ie & 0xFFFE;
// 2G cart with 6600M0U0BE (369-in-1)
_FLASH_WRITE(0, 0xFF);
_FLASH_WRITE(0, 0x90);
data = *(vu32 *)AGB_ROM;
_FLASH_WRITE(0, 0xFF);
if (data == 0x88B0008A)
{
REG_IE = ie;
flash_type = 1;
flash_sector_size = 0x40000;
FlashCalcOffsets();
return;
}
// 512M cart with MSP55LV100S (Zelda Classic Collection 7-in-1)
_FLASH_WRITE(0, 0xF0F0);
_FLASH_WRITE(0xAAA, 0xAAA9);
_FLASH_WRITE(0x555, 0x5556);
_FLASH_WRITE(0xAAA, 0x9090);
data = *(vu32 *)AGB_ROM;
_FLASH_WRITE(0, 0xF0F0);
if (data == 0x7E7D0102)
{
REG_IE = ie;
flash_type = 2;
flash_sector_size = 0x20000;
FlashCalcOffsets();
return;
}
// Unknown type
REG_IE = ie;
flash_type = 0;
flash_sector_size = 0x20000;
FlashCalcOffsets();
return;
}
IWRAM_CODE void FlashEraseSector(u32 address)
{
if (flash_type == 0)
{
FlashDetectType();
}
vu8 _flash_type = flash_type;
vu16 ie = REG_IE;
REG_IE = ie & 0xFFFE;
if (_flash_type == 1)
{
_FLASH_WRITE(address, 0xFF);
_FLASH_WRITE(address, 0x60);
_FLASH_WRITE(address, 0xD0);
_FLASH_WRITE(address, 0x20);
_FLASH_WRITE(address, 0xD0);
while (1)
{
__asm("nop");
if ((*((vu16 *)(AGB_ROM + address)) & 0x80) == 0x80)
{
break;
}
}
_FLASH_WRITE(address, 0xFF);
}
else if (_flash_type == 2)
{
_FLASH_WRITE(0xAAA, 0xAAA9);
_FLASH_WRITE(0x555, 0x5556);
_FLASH_WRITE(0xAAA, 0x8080);
_FLASH_WRITE(0xAAA, 0xAAA9);
_FLASH_WRITE(0x555, 0x5556);
_FLASH_WRITE(address, 0x3030);
while (1)
{
__asm("nop");
if ((*((vu16 *)(AGB_ROM + address))) == 0xFFFF)
{
break;
}
}
_FLASH_WRITE(address, 0xF0F0);
}
REG_IE = ie;
}
IWRAM_CODE void FlashWriteData(u32 address, u32 length)
{
if (flash_type == 0)
{
FlashDetectType();
}
u8 _flash_type = flash_type;
vu16 *p_rom = (vu16 *)(AGB_ROM + address);
vu16 ie = REG_IE;
REG_IE = ie & 0xFFFE;
if (_flash_type == 1)
{
for (int j = 0; j < (int)(length / 0x400); j++)
{
_FLASH_WRITE(address + (j * 0x400), 0xEA);
while (1)
{
__asm("nop");
if ((p_rom[(j * 0x200)] & 0x80) == 0x80)
{
break;
}
}
_FLASH_WRITE(address + (j * 0x400), 0x1FF);
for (int i = 0; i < 0x400; i += 2)
{
_FLASH_WRITE(address + (j * 0x400) + i, data_buffer[(j * 0x400) + i + 1] << 8 | data_buffer[(j * 0x400) + i]);
}
_FLASH_WRITE(address + (j * 0x400), 0xD0);
while (1)
{
__asm("nop");
if ((p_rom[(j * 0x200)] & 0x80) == 0x80)
{
break;
}
}
}
_FLASH_WRITE(address, 0xFF);
}
else if (_flash_type == 2)
{
for (int j = 0; j < (int)(length / 0x20); j++)
{
_FLASH_WRITE(0xAAA, 0xAAA9);
_FLASH_WRITE(0x555, 0x5556);
_FLASH_WRITE(address + (j * 0x20), 0x2526);
_FLASH_WRITE(address + (j * 0x20), 0x0F0F);
u16 data = 0;
for (int i = 0; i < 0x20; i += 2)
{
__asm("nop");
data = data_buffer[(j * 0x20) + i + 1] << 8 | data_buffer[(j * 0x20) + i];
_FLASH_WRITE(address + (j * 0x20) + i, data);
}
_FLASH_WRITE(address + (j * 0x20), 0x292A);
while (1)
{
__asm("nop");
if (p_rom[(j * 0x10) + 0x0F] == data)
{
break;
}
}
}
_FLASH_WRITE(address, 0xF0F0);
}
REG_IE = ie;
}
IWRAM_CODE void DrawBootStatusLine(u8 begin, u8 end)
{
for (int i = begin; i < end; i++)
{
((u16 *)AGB_VRAM)[(120 * 159) + i] = 0;
}
}
IWRAM_CODE u8 BootGame(ItemConfig config, FlashStatus status)
{
u32 _flash_sector_size = flash_sector_size;
u32 _flash_save_block_offset = flash_save_sector_offset;
u32 _flash_status_block_offset = flash_status_sector_offset;
// Check if supported flash chip is present
FlashDetectType();
u8 _flash_type = flash_type;
if (_flash_type == 0)
return 1;
// Temporarily store SRAM values 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;
// Write previous SRAM to flash
if (status.battery_present) {
if (status.last_boot_save_type != SRAM_NONE)
{
for (int i = 0; i < SRAM_SIZE; i++)
{
data_buffer[i] = ((vu8 *)AGB_SRAM)[i];
}
data_buffer[2] = sram_register_backup[0];
data_buffer[3] = sram_register_backup[1];
data_buffer[4] = sram_register_backup[2];
data_buffer[5] = sram_register_backup[3];
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);
}
// Disable SRAM access
*(vu8 *)MAPPER_CONFIG4 = 0;
// Read new SRAM from flash
if (config.save_type != SRAM_NONE)
{
for (int i = 0; i < SRAM_SIZE; i += 2)
{
data_buffer[i] = *((vu16 *)(AGB_ROM + ((_flash_save_block_offset + config.save_index) * _flash_sector_size) + i)) & 0xFF;
data_buffer[i + 1] = *((vu16 *)(AGB_ROM + ((_flash_save_block_offset + config.save_index) * _flash_sector_size) + i)) >> 8;
}
}
// Fade out
REG_BLDCNT = 0x00FF;
for (u8 i = 0; i < 17; i++)
{
REG_BLDY = i;
SystemCall(5);
}
// Disable interrupts
REG_IE = 0;
// Set mapper configuration
*(vu8 *)MAPPER_CONFIG1 = ((config.rom_offset / 0x40) & 0xF) << 4; // flash bank (0~7)
*(vu8 *)MAPPER_CONFIG2 = 0x40 + (config.rom_offset % 0x40); // ROM offset (in 512 KB blocks) within current flash bank
*(vu8 *)MAPPER_CONFIG3 = 0x40 - config.rom_size; // accessible ROM size (in 512 KB blocks)
// Wait until menu ROM is no longer visible
u16 timeout = 0xFFFF;
while (((vu16 *)AGB_ROM)[0x58] == 0x4B4C)
{
if (!timeout--)
{
REG_IE = 1;
REG_BLDY = 0;
SystemCall(5);
return 2;
}
}
// Lock mapper
*(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
for (int i = 0; i < SRAM_SIZE; i++)
{
((vu8 *)AGB_SRAM)[i] = data_buffer[i];
}
// Clear palette
for (int i = 0; i < SCREEN_WIDTH * SCREEN_HEIGHT >> 1; i++)
{
((vu16 *)AGB_VRAM)[i] = 0;
}
REG_BLDY = 0;
// Soft Reset system call
__asm("swi 0"); // Soft reset
return 3;
}

35
source/flash.h Normal file
View File

@ -0,0 +1,35 @@
/*
GBA Multi Game Menu
Author: Lesserkuma (github.com/lesserkuma)
*/
#ifndef FLASH_H_
#define FLASH_H_
#include "main.h"
#define _FLASH_WRITE(pa, pd) \
{ \
*(((vu16 *)AGB_ROM) + ((pa) >> 1)) = pd; \
__asm("nop"); \
}
#define MAGIC_FLASH_STATUS 0x414D554B
#define FLASH_STATUS_LOCATION 0xA0000
typedef struct __attribute__((packed)) FlashStatus_
{
u32 magic;
u8 version;
u8 battery_present;
u16 last_boot_menu_index;
u8 last_boot_save_index;
SAVE_TYPE last_boot_save_type;
} FlashStatus;
IWRAM_CODE void FlashDetectType(void);
IWRAM_CODE void FlashEraseSector(u32 address);
IWRAM_CODE void FlashWriteData(u32 address, u32 length);
IWRAM_CODE u8 BootGame(ItemConfig config, FlashStatus status);
#endif

321
source/font.c Normal file
View File

@ -0,0 +1,321 @@
/*
GBA Multi Game Menu
Author: Lesserkuma (github.com/lesserkuma)
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "font.h"
FontSpecs sFontSpecs;
NFTR_Header sNFTR_Header;
FINF_Header sFINF_Header;
CGLP_Header sCGLP_Header;
CWDH_Header sCWDH_Header;
CMAP_Header sCMAP_Header;
u16 FallbackCharacter;
u16 ArrowCharacter;
s8 FontMarginTop;
s8 FontMarginBottom;
u8 last_font = -1;
const u8* font;
void LoadFont(u8 index) {
if (index != last_font) {
font = font_nftr;
FallbackCharacter = 0x2753;
ArrowCharacter = 0x21E8;
FontMarginTop = 0;
FontMarginBottom = 0;
#ifdef FONT_NTR_IPL
if (index == 1) {
font = NTR_IPL_font_s_nftr;
FallbackCharacter = 0xE011;
ArrowCharacter = 0xE019;
FontMarginTop = 2;
FontMarginBottom = 3;
}
#endif
#ifdef FONT_TBF1
if (index == 2) {
font = TBF1_s_nftr;
FallbackCharacter = 0xE011;
ArrowCharacter = 0xE019;
FontMarginTop = 1;
FontMarginBottom = 1;
}
#endif
#ifdef FONT_TBF1_CN
if (index == 3) {
font = TBF1_cn_s_nftr;
FallbackCharacter = 0xE011;
ArrowCharacter = 0xE019;
FontMarginTop = 2;
FontMarginBottom = 2;
}
#endif
#ifdef FONT_TBF1_KR
if (index == 4) {
font = TBF1_kr_s_nftr;
FallbackCharacter = 0xE011;
ArrowCharacter = 0xE019;
FontMarginTop = 2;
FontMarginBottom = 2;
}
#endif
#ifdef FONT_TWL_IRAJ_1
if (index == 5) {
font = TWL_IRAJ_1_nftr;
FallbackCharacter = 0xFF1F;
ArrowCharacter = 0x2192;
FontMarginTop = 4;
FontMarginBottom = 6;
}
#endif
LoadNFTR(font);
last_font = index;
}
}
void LoadNFTR(const u8* nftr_data) {
u32 pos = 0;
memcpy(&sNFTR_Header, nftr_data+pos, sizeof(sNFTR_Header));
pos += sNFTR_Header.size;
memcpy(&sFINF_Header, nftr_data+pos, sizeof(sFINF_Header));
pos += sFINF_Header.size;
memcpy(&sCGLP_Header, nftr_data+pos, sizeof(sCGLP_Header));
sFontSpecs.cglp_offset = pos;
pos = sFINF_Header.offset_CWDH - 8;
memcpy(&sCWDH_Header, nftr_data+pos, sizeof(sCWDH_Header));
sFontSpecs.cwdh_offset = pos + 16;
pos = sFINF_Header.offset_CMAP - 8;
memcpy(&sCMAP_Header, nftr_data+pos, sizeof(sCMAP_Header));
sFontSpecs.cmap_offset = pos;
sFontSpecs.nftr_version = sNFTR_Header.version;
sFontSpecs.max_width = sCGLP_Header.max_width;
sFontSpecs.max_height = sCGLP_Header.max_height;
sFontSpecs.bytes_per_char = sCGLP_Header.bytes_per_char;
sFontSpecs.num_of_chars = sCWDH_Header.num_of_chars;
sFontSpecs.bpp = sCGLP_Header.bpp;
}
u16 GetFontIndex(u16 ch, const u8* nftr_data) {
u32 pos = sFontSpecs.cmap_offset;
while (TRUE) {
if (pos <= 0) return 0xFFFF;
CMAP_Header tCMAP_Header;
memcpy(&tCMAP_Header, nftr_data+pos, sizeof(tCMAP_Header));
pos += 20;
if (ch < tCMAP_Header.start_code || ch > tCMAP_Header.end_code) {
// CMAP doesn't have this character
pos = tCMAP_Header.next_offset - 8;
continue;
}
if (tCMAP_Header.type == 0) {
u16 index_offset = nftr_data[pos+1] << 8 | nftr_data[pos];
return ch - tCMAP_Header.start_code + index_offset;
} else if (tCMAP_Header.type == 1) {
u16 utf16le_index = tCMAP_Header.start_code;
for (u16 i = tCMAP_Header.start_code; i < tCMAP_Header.end_code; i++) {
u16 font_index = nftr_data[pos+1] << 8 | nftr_data[pos];
pos += 2;
if (utf16le_index == ch) return font_index;
utf16le_index += 1;
}
} else if (tCMAP_Header.type == 2) {
u16 index_offset = nftr_data[pos+1] << 8 | nftr_data[pos];
pos += 2;
for (u16 i = 0; i < index_offset; i++) {
u16 utf16le_index = nftr_data[pos+1] << 8 | nftr_data[pos];
pos += 2;
u16 font_index = nftr_data[pos+1] << 8 | nftr_data[pos];
pos += 2;
if (utf16le_index == ch) return font_index;
utf16le_index += 1;
}
}
return 0xFFFF;
}
}
void GetFontWidths(u16 index, const u8* nftr_data, u8* a, u8* b, u8* c) {
u32 pos = sFontSpecs.cwdh_offset;
pos = pos + (index * 3);
*a = nftr_data[pos++];
*b = nftr_data[pos++];
*c = nftr_data[pos++];
}
void AsciiToUnicode(char* text, u16* output) {
for (u8 i = 0; i < 64; i++) {
if (text[i] == 0) break;
output[i] = text[i];
}
}
void DrawText(u8 px, u8 py, u8 align, u16* text, u8 length, const u8* nftr_data, volatile void* vram, BOOL highlighted) {
u8 pos_left = 0;
u8 glyph_width = 0;
u8 glyph_left = 0;
u8 pixels[sFontSpecs.max_width * sFontSpecs.max_height];
u8 canvas[SCREEN_WIDTH * sFontSpecs.max_height];
u8 color_modifier = 0;
u32 offset = 0;
py += FontMarginTop;
if (highlighted) {
color_modifier = 10;
}
memset(canvas, 255, SCREEN_WIDTH * sFontSpecs.max_height);
for (u8 i = 0; i < length; i++) {
u8 a, b, c = 0;
u16 ch = text[i];
if (ch == 0x0000 || ch == 0xFFFF) {
break;
}
BOOL last_ch = FALSE;
while (1) {
u16 index = GetFontIndex(ch, nftr_data);
if (index == 0xFFFF) { // character not found
ch = FallbackCharacter;
continue;
}
if (pos_left + sFontSpecs.max_width + (sFontSpecs.max_width >> 1) >= SCREEN_WIDTH - SCREEN_MARGIN_RIGHT - px) {
if (ch != 0x2026) {
ch = 0x2026; // ...
last_ch = TRUE;
continue;
}
}
GetFontWidths(index, nftr_data, &a, &b, &c);
offset = index * sFontSpecs.bytes_per_char;
glyph_left = a;
if (glyph_left >= sFontSpecs.max_width) glyph_left = 0;
if (ch == 32) { // space
glyph_left = 0;
}
glyph_width = c - glyph_left;
break;
}
if (pos_left + glyph_width >= SCREEN_WIDTH - px) {
break;
}
if (b == 0) {
glyph_width = c;
}
if (sFontSpecs.nftr_version == 1) {
if (glyph_width == 0) glyph_width = sFontSpecs.max_width;
glyph_width += 1;
}
const u8* gl = &nftr_data[sFontSpecs.cglp_offset + 0x10 + offset];
u16 pixel_pos = 0;
if (align == ALIGN_LEFT) { // Draw to VRAM directly
if (sFontSpecs.bpp == 1) {
for (u8 a = 0; a < sFontSpecs.bytes_per_char; a++) {
for (u8 b = 0; b < 8; b++) {
pixels[pixel_pos++] = ((gl[a] >> (7 - b)) & 1);
}
}
for (u8 x = 0; x < sFontSpecs.max_height; x++) {
for (u8 y = 0; y < sFontSpecs.max_width; y++) {
u8 p = pixels[(x * sFontSpecs.max_width) + y];
if (p == 1) {
SetPixel(vram, py + x, px + y + pos_left, 254 - color_modifier);
}
}
}
} else if (sFontSpecs.bpp == 2) {
for (u8 a = 0; a < sFontSpecs.bytes_per_char; a++) {
for (u8 b = 0; b < 8; b += sFontSpecs.bpp) {
u8 p = 0;
for (u8 bit = 0; bit < sFontSpecs.bpp; bit++) {
p |= ((gl[a] >> (7 - (b + bit))) & 1) << bit;
}
pixels[pixel_pos++] = p;
}
}
for (u8 x = 0; x < sFontSpecs.max_height; x++) {
for (u8 y = 0; y < sFontSpecs.max_width; y++) {
u8 p = pixels[(x * sFontSpecs.max_width) + y];
if (p != 0) {
SetPixel(vram, py + x, px + y + pos_left, p + 250 - color_modifier);
}
}
}
}
} else {
if (sFontSpecs.bpp == 1) {
for (u8 a = 0; a < sFontSpecs.bytes_per_char; a++) {
for (u8 b = 0; b < 8; b++) {
pixels[pixel_pos++] = ((gl[a] >> (7 - b)) & 1);
}
}
for (u8 x = 0; x < sFontSpecs.max_height; x++) {
for (u8 y = 0; y < sFontSpecs.max_width; y++) {
u8 p = pixels[(x * sFontSpecs.max_width) + y];
if (p == 1) {
canvas[(x * SCREEN_WIDTH) + y + pos_left] = 254 - color_modifier;
}
}
}
} else if (sFontSpecs.bpp == 2) {
for (u8 a = 0; a < sFontSpecs.bytes_per_char; a++) {
for (u8 b = 0; b < 8; b += sFontSpecs.bpp) {
u8 p = 0;
for (u8 bit = 0; bit < sFontSpecs.bpp; bit++) {
p |= ((gl[a] >> (7 - (b + bit))) & 1) << bit;
}
pixels[pixel_pos++] = p;
}
}
for (u8 x = 0; x < sFontSpecs.max_height; x++) {
for (u8 y = 0; y < sFontSpecs.max_width; y++) {
u8 p = pixels[(x * sFontSpecs.max_width) + y];
if (p != 0) {
canvas[(x * SCREEN_WIDTH) + y + pos_left] = p + 250 - color_modifier;
}
}
}
}
}
pos_left += glyph_width;
if (last_ch) break;
}
if (align == ALIGN_LEFT) return;
if (align == ALIGN_CENTER) {
px = (SCREEN_WIDTH - pos_left) >> 1;
} else if (align == ALIGN_RIGHT) {
px = SCREEN_WIDTH - pos_left - px + 1;
}
u16 c = 0;
for (u8 x = 0; x < sFontSpecs.max_height; x++) {
for (u8 y = 0; y < SCREEN_WIDTH; y++) {
if (y > pos_left) {
c++;
continue;
}
if (canvas[c] != 255) {
SetPixel(vram, py + x, px + y, canvas[c]);
}
c++;
}
}
}

119
source/font.h Normal file
View File

@ -0,0 +1,119 @@
/*
GBA Multi Game Menu
Author: Lesserkuma (github.com/lesserkuma)
*/
#ifndef FONT_H_
#define FONT_H_
#include "main.h"
#include "font_nftr.h"
#if __has_include("NTR_IPL_font_s_nftr.h")
#define FONT_NTR_IPL
#include "NTR_IPL_font_s_nftr.h"
#endif
#if __has_include("TBF1_s_nftr.h")
#define FONT_TBF1
#include "TBF1_s_nftr.h"
#endif
#if __has_include("TBF1-cn_s_nftr.h")
#define FONT_TBF1_CN
#include "TBF1-cn_s_nftr.h"
#endif
#if __has_include("TBF1-kr_s_nftr.h")
#define FONT_TBF1_KR
#include "TBF1-kr_s_nftr.h"
#endif
#if __has_include("TWL-IRAJ-1_nftr.h")
#define FONT_TWL_IRAJ_1
#include "TWL-IRAJ-1_nftr.h"
#endif
#define MAGIC_NFTR 0x4E465452
#define MAGIC_FINF 0x46494E46
#define MAGIC_CGLP 0x43474C50
#define MAGIC_CMAP 0x434D4150
#define MAGIC_CWDH 0x43574448
#define ALIGN_LEFT 0
#define ALIGN_CENTER 1
#define ALIGN_RIGHT 2
typedef struct FontSpecs_
{
u32 cmap_offset;
u32 cwdh_offset;
u32 cglp_offset;
u32 num_of_chars;
u8 max_width;
u8 max_height;
u8 bytes_per_char;
u8 bpp;
u8 nftr_version;
u16 fallback_char;
} FontSpecs;
typedef struct NFTR_Header_
{
u32 magic;
u16 byteorder;
u8 version;
u8 unknown1;
u32 file_size;
u16 size;
u16 chunks_num;
} NFTR_Header;
typedef struct FINF_Header_
{
u32 magic;
u32 size;
u32 unknown1;
u32 unknown2;
u32 unknown3;
u32 offset_CWDH;
u32 offset_CMAP;
} FINF_Header;
typedef struct CGLP_Header_
{
u32 magic;
u32 size;
u8 max_width;
u8 max_height;
u16 bytes_per_char;
u16 unknown1;
u8 bpp;
u8 orientation;
} CGLP_Header;
typedef struct CWDH_Header_
{
u32 magic;
u32 size;
u16 unknown1;
u16 num_of_chars;
u32 unknown2;
} CWDH_Header;
typedef struct CMAP_Header_
{
u32 magic;
u32 size;
u16 start_code;
u16 end_code;
u16 type;
u16 unknown1;
u32 next_offset;
} CMAP_Header;
void LoadFont(u8 index);
void LoadNFTR(const u8 *nftr_data);
u16 GetFontIndex(u16 ch, const u8 *nftr_data);
void GetFontWidths(u16 index, const u8 *nftr_data, u8 *a, u8 *b, u8 *c);
void AsciiToUnicode(char *text, u16 *output);
void DrawText(u8 px, u8 py, u8 align, u16 *text, u8 length, const u8 *nftr_data, volatile void *canvas, BOOL highlighted);
#endif

288
source/main.c Normal file
View File

@ -0,0 +1,288 @@
/*
GBA Multi Game Menu
Author: Lesserkuma (github.com/lesserkuma)
*/
#include <gba.h>
#include <gba_console.h>
#include <gba_video.h>
#include <gba_interrupt.h>
#include <gba_input.h>
#include <gba_dma.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "main.h"
#include "font.h"
#include "flash.h"
extern FontSpecs sFontSpecs;
extern u16 FallbackCharacter;
extern u16 ArrowCharacter;
extern s8 FontMarginTop;
extern s8 FontMarginBottom;
extern const u8* font;
extern u8 *itemlist;
extern u8 flash_type;
extern u32 flash_sector_size;
extern u32 flash_itemlist_sector_offset;
extern u32 flash_status_sector_offset;
extern u32 flash_save_sector_offset;
extern u8 data_buffer[0x10000];
ItemConfig sItemConfig;
FlashStatus sFlashStatus;
void SetPixel(volatile u16* buffer, u8 row, u8 col, u8 color) {
/* https://ianfinlayson.net/class/cpsc305/notes/09-graphics */
u16 offset = (row * SCREEN_WIDTH + col) >> 1;
u16 pixel = buffer[offset];
if (col & 1) {
buffer[offset] = (color << 8) | (pixel & 0x00FF);
} else {
buffer[offset] = (pixel & 0xFF00) | color;
}
}
void ClearList(void* vram, u8 top, u8 height) {
dmaCopy(bgBitmap + (top * (SCREEN_WIDTH >> 2)), (void*)AGB_VRAM+0xA000 + (top * SCREEN_WIDTH), SCREEN_WIDTH * height);
}
int main(void) {
char temp_ascii[64];
u16 temp_unicode[64];
s8 page_active = 0;
u8 page_total = 64;
u16 roms_total = 0;
s8 cursor_pos = 0;
u8 redraw_items = 0xFF;
u8 roms_page = 7;
u16 kHeld = 0;
BOOL show_debug = FALSE;
BOOL show_credits = FALSE;
BOOL boot_failed = FALSE;
irqInit();
irqEnable(IRQ_VBLANK);
FlashDetectType();
// Load palette
memset((void*)AGB_VRAM, 255, SCREEN_WIDTH * SCREEN_HEIGHT * 2);
dmaCopy(bgPal, BG_PALETTE, 256 * 2);
((u16*)AGB_PRAM)[250] = 0xFFFF;
((u16*)AGB_PRAM)[251] = 0xB18C;
((u16*)AGB_PRAM)[252] = 0xDEF7;
((u16*)AGB_PRAM)[253] = 0x9084;
((u16*)AGB_PRAM)[254] = 0x8000;
((u16*)AGB_PRAM)[255] = 0xFFFF;
((u16*)AGB_PRAM)[240] = 0xFFFF;
((u16*)AGB_PRAM)[241] = 0xDD8C;
((u16*)AGB_PRAM)[242] = 0xFAF7;
((u16*)AGB_PRAM)[243] = 0xC084;
((u16*)AGB_PRAM)[244] = 0xC084;
((u16*)AGB_PRAM)[245] = 0xFFFF;
VBlankIntrWait();
// Load background
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;
}
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));
if ((sFlashStatus.magic != MAGIC_FLASH_STATUS) || (sFlashStatus.last_boot_menu_index > roms_total)) {
sFlashStatus.magic = MAGIC_FLASH_STATUS;
sFlashStatus.version = 0;
sFlashStatus.battery_present = 1;
sFlashStatus.last_boot_menu_index = 0xFFFF;
sFlashStatus.last_boot_save_index = 0xFF;
sFlashStatus.last_boot_save_type = SRAM_NONE;
} else {
cursor_pos = sFlashStatus.last_boot_menu_index % 8;
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;
}
s32 wait = 0;
u8 f = 0;
while (1) {
if (redraw_items != 0) {
if (redraw_items == 0xFF) {
// Full redraw (new page etc.)
roms_page = 7;
for (u8 i = 0; i < 8; i++) {
if ((page_active * 8 + i) >= roms_total) {
roms_page = i - 1;
break;
}
}
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));
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);
}
} else {
// 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));
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);
}
}
}
memcpy(&sItemConfig, ((u8*)itemlist)+0x70*(page_active*8+cursor_pos), sizeof(sItemConfig));
// Draw cursor
LoadFont(1);
ClearList((void*)AGB_VRAM+0xA000, SCREEN_HEIGHT - sFontSpecs.max_height - 1, sFontSpecs.max_height);
u16 arrow[1] = { ArrowCharacter };
DrawText(14, 26+cursor_pos*14, ALIGN_LEFT, (u16*)&arrow, 1, font, (void*)AGB_VRAM+0xA000, FALSE);
// Draw status bar
LoadFont(2);
memset(temp_unicode, 0, sizeof(temp_unicode));
snprintf(temp_ascii, 10+1, "%d/%d", page_active*8+cursor_pos+1, roms_total);
AsciiToUnicode(temp_ascii, temp_unicode);
DrawText(11, SCREEN_HEIGHT - sFontSpecs.max_height - 3 - FontMarginBottom, ALIGN_RIGHT, temp_unicode, 10, font, (void*)AGB_VRAM+0xA000, FALSE);
if (boot_failed) {
LoadFont(0);
if (boot_failed == 1) {
DrawText(5, SCREEN_HEIGHT - sFontSpecs.max_height - 3 - FontMarginBottom, ALIGN_LEFT, u"Error: Unsupported cartridge!", 48, font, (void*)AGB_VRAM+0xA000, FALSE);
} else if (boot_failed == 2) {
DrawText(5, SCREEN_HEIGHT - sFontSpecs.max_height - 3 - FontMarginBottom, ALIGN_LEFT, u"Error: Mapper is not responding!", 48, font, (void*)AGB_VRAM+0xA000, FALSE);
} else {
DrawText(5, SCREEN_HEIGHT - sFontSpecs.max_height - 3 - FontMarginBottom, ALIGN_LEFT, u"Error: Game couldn't be launched!", 48, font, (void*)AGB_VRAM+0xA000, FALSE);
}
} else if (show_credits) {
LoadFont(0);
#ifdef __TIMESTAMP_ISO__
memset(temp_unicode, 0, sizeof(temp_unicode));
snprintf(temp_ascii, 48, "Menu by LK - %s", __TIMESTAMP_ISO__);
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);
#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) {
LoadFont(0);
u8 a = ((sItemConfig.rom_offset / 0x40) & 0xF) << 4;
u8 b = 0x40 + (sItemConfig.rom_offset % 0x40);
u8 c = 0x40 - sItemConfig.rom_size;
snprintf(temp_ascii, 64, "%02X:%02X:%02X|0x%X~%dMiB|%X", a, b, c, (int)(sItemConfig.rom_offset * 512 * 1024), (int)(sItemConfig.rom_size * 512 >> 10), (int)(flash_save_sector_offset + sItemConfig.save_index));
memset(temp_unicode, 0, sizeof(temp_unicode));
AsciiToUnicode(temp_ascii, temp_unicode);
DrawText(6, SCREEN_HEIGHT - sFontSpecs.max_height - 3 - FontMarginBottom, ALIGN_LEFT, temp_unicode, 64, font, (void*)AGB_VRAM+0xA000, FALSE);
}
// VRAM bank swapping
REG_DISPCNT ^= 0x0010;
dmaCopy((void*)AGB_VRAM+0xA000, (void*)AGB_VRAM, SCREEN_WIDTH * SCREEN_HEIGHT);
REG_DISPCNT ^= 0x0010;
redraw_items = 0;
}
// Check for menu keys
scanKeys();
kHeld = keysHeld();
if (kHeld != 0) {
wait++;
} else {
f = 0;
}
if (((kHeld & 0x3FF) && !f) || (wait > 1000)) {
if (!f) {
wait = -8000;
} else {
wait = 0;
}
f = 1;
if (boot_failed) {
SystemCall(0); // Soft reset
}
if ((kHeld & KEY_A) || (kHeld & KEY_START)) {
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);
}
REG_DISPCNT ^= 0x0010;
dmaCopy((void*)AGB_VRAM+0xA000, (void*)AGB_VRAM, SCREEN_WIDTH * SCREEN_HEIGHT);
REG_DISPCNT ^= 0x0010;
}
u8 error_code = BootGame(sItemConfig, sFlashStatus);
boot_failed = error_code;
redraw_items = 0xFF;
REG_IE = 1;
} else if (kHeld & KEY_B) {
SystemCall(0); // Soft reset
} else if ((kHeld & KEY_LEFT) || (kHeld & KEY_RIGHT)) {
if (kHeld & KEY_LEFT) {
page_active--;
} else if (kHeld & KEY_RIGHT) {
page_active++;
}
if (page_active > page_total - 1) page_active = 0;
if (page_active < 0) page_active = page_total - 1;
redraw_items = 0xFF;
} else if ((kHeld & KEY_UP) || (kHeld & KEY_DOWN)) {
redraw_items |= 1 << cursor_pos;
if (kHeld & KEY_UP)
cursor_pos--;
else if (kHeld & KEY_DOWN)
cursor_pos++;
if (cursor_pos < 0) {
cursor_pos = 7;
page_active--;
redraw_items = 0xFF;
} else if (cursor_pos > roms_page) {
cursor_pos = 0;
page_active++;
redraw_items = 0xFF;
}
if (page_active > page_total - 1) page_active = 0;
if (page_active < 0) page_active = page_total - 1;
redraw_items |= 1 << cursor_pos;
}
}
}
while (1) { VBlankIntrWait(); }
}

78
source/main.h Normal file
View File

@ -0,0 +1,78 @@
/*
GBA Multi Game Menu
Author: Lesserkuma (github.com/lesserkuma)
*/
#ifndef MAIN_H_
#define MAIN_H_
#include "bg.h"
typedef uint32_t u32;
typedef uint16_t u16;
typedef uint8_t u8;
typedef int32_t s32;
typedef int16_t s16;
typedef int8_t s8;
typedef uint8_t BOOL;
#define FALSE 0
#define TRUE 1
#define AGB_ROM (volatile void *)0x8000000
#define AGB_PRAM (volatile void *)0x5000000
#define AGB_VRAM (volatile void *)0x6000000
#define AGB_SRAM (volatile void *)0xE000000
#define MAPPER_CONFIG1 (volatile void *)0xE000002
#define MAPPER_CONFIG2 (volatile void *)0xE000003
#define MAPPER_CONFIG3 (volatile void *)0xE000004
#define MAPPER_CONFIG4 (volatile void *)0xE000005
#define SRAM_SIZE 64 * 1024
#define SCREEN_WIDTH 240
#define SCREEN_HEIGHT 160
#define SCREEN_MARGIN_RIGHT 7
#define V5bit(x) ((x) >> 3)
#define RGB555(r, g, b) ((V5bit(r) << 0) | (V5bit(g) << 5) | (V5bit(b) << 10) | (((1) & 1) << 15))
#define RGB555_CLEAR 0
#define RGB555_BLACK RGB555(0x00, 0x00, 0x00)
#define RGB555_WHITE RGB555(0xFF, 0xFF, 0xFF)
#define RGB555_RED RGB555(0xFF, 0x00, 0x00)
#define RGB555_GREEN RGB555(0x00, 0xFF, 0x00)
#define RGB555_BLUE RGB555(0x00, 0x00, 0xFF)
#define RGB555_PURPLE RGB555(0xFF, 0x00, 0xFF)
#define RGB555_YELLOW RGB555(0xFF, 0xFF, 0x00)
#define RGB555_GREY RGB555(0x7F, 0x7F, 0x7F)
#define RGB555_MILK RGB555(0x94, 0x94, 0x94)
#define _DIV_CEIL(a, b) ((a / b) + ((a % b) != 0))
typedef enum u8
{
SRAM_NONE,
SRAM_256K,
SRAM_512K,
SRAM_1M
} SAVE_TYPE;
typedef struct ItemConfig_
{
u8 font;
u8 title_length;
u16 rom_offset;
u16 rom_size;
SAVE_TYPE save_type;
u8 save_index;
u16 index;
u8 reserved[6];
u16 title[0x30];
} ItemConfig;
extern char __rom_end__;
void SetPixel(volatile u16 *buffer, u8 row, u8 col, u8 color);
#endif