diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..bf3feed --- /dev/null +++ b/Makefile @@ -0,0 +1,189 @@ +#--------------------------------------------------------------------------------- +.SUFFIXES: +#--------------------------------------------------------------------------------- + +ifeq ($(strip $(DEVKITARM)),) +$(error "Please set DEVKITARM in your environment. export DEVKITARM=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 +#--------------------------------------------------------------------------------------- diff --git a/README.md b/README.md index 0a90f42..0e8370b 100644 --- a/README.md +++ b/README.md @@ -1 +1,68 @@ -# GBA_MultiMenu \ No newline at end of file +# 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. diff --git a/fonts/Fusion Pixel/LICENSE.txt b/fonts/Fusion Pixel/LICENSE.txt new file mode 100644 index 0000000..03919e6 --- /dev/null +++ b/fonts/Fusion Pixel/LICENSE.txt @@ -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. diff --git a/fonts/Fusion Pixel/fusion-pixel-12px-proportional.ttf b/fonts/Fusion Pixel/fusion-pixel-12px-proportional.ttf new file mode 100644 index 0000000..2f5ae54 Binary files /dev/null and b/fonts/Fusion Pixel/fusion-pixel-12px-proportional.ttf differ diff --git a/fonts/font.nftr b/fonts/font.nftr new file mode 100644 index 0000000..2032c0f Binary files /dev/null and b/fonts/font.nftr differ diff --git a/graphics/bg.grit b/graphics/bg.grit new file mode 100644 index 0000000..f335851 --- /dev/null +++ b/graphics/bg.grit @@ -0,0 +1,5 @@ +# 8 bit bitmap +-gB8 + +# bitmap format +-gb diff --git a/graphics/bg.png b/graphics/bg.png new file mode 100644 index 0000000..82a3117 Binary files /dev/null and b/graphics/bg.png differ diff --git a/rom_builder/rom_builder.py b/rom_builder/rom_builder.py new file mode 100644 index 0000000..a520465 --- /dev/null +++ b/rom_builder/rom_builder.py @@ -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_.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="don’t wait for user input when finished", action="store_true", default=False) +parser.add_argument("--no-log", help="don’t 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}” couldn’t 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("", 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]) diff --git a/rom_builder/roms/- ROM files go here - b/rom_builder/roms/- ROM files go here - new file mode 100644 index 0000000..e69de29 diff --git a/source/flash.c b/source/flash.c new file mode 100644 index 0000000..7069ba0 --- /dev/null +++ b/source/flash.c @@ -0,0 +1,320 @@ +/* +GBA Multi Game Menu +Author: Lesserkuma (github.com/lesserkuma) +*/ + +#include +#include +#include + +#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; +} diff --git a/source/flash.h b/source/flash.h new file mode 100644 index 0000000..5f2acd0 --- /dev/null +++ b/source/flash.h @@ -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 diff --git a/source/font.c b/source/font.c new file mode 100644 index 0000000..24cf617 --- /dev/null +++ b/source/font.c @@ -0,0 +1,321 @@ +/* +GBA Multi Game Menu +Author: Lesserkuma (github.com/lesserkuma) +*/ + +#include +#include +#include + +#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++; + } + } +} diff --git a/source/font.h b/source/font.h new file mode 100644 index 0000000..a5bed92 --- /dev/null +++ b/source/font.h @@ -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 diff --git a/source/main.c b/source/main.c new file mode 100644 index 0000000..d970a72 --- /dev/null +++ b/source/main.c @@ -0,0 +1,288 @@ +/* +GBA Multi Game Menu +Author: Lesserkuma (github.com/lesserkuma) +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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(); } +} diff --git a/source/main.h b/source/main.h new file mode 100644 index 0000000..b25cd73 --- /dev/null +++ b/source/main.h @@ -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