mirror of
https://github.com/lesserkuma/GBA_MultiMenu.git
synced 2026-03-23 10:14:15 -05:00
-
This commit is contained in:
parent
49a9b072cc
commit
08cc7b6bc8
189
Makefile
Normal file
189
Makefile
Normal 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
|
||||
#---------------------------------------------------------------------------------------
|
||||
69
README.md
69
README.md
|
|
@ -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.
|
||||
|
|
|
|||
398
fonts/Fusion Pixel/LICENSE.txt
Normal file
398
fonts/Fusion Pixel/LICENSE.txt
Normal 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.
|
||||
BIN
fonts/Fusion Pixel/fusion-pixel-12px-proportional.ttf
Normal file
BIN
fonts/Fusion Pixel/fusion-pixel-12px-proportional.ttf
Normal file
Binary file not shown.
BIN
fonts/font.nftr
Normal file
BIN
fonts/font.nftr
Normal file
Binary file not shown.
5
graphics/bg.grit
Normal file
5
graphics/bg.grit
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# 8 bit bitmap
|
||||
-gB8
|
||||
|
||||
# bitmap format
|
||||
-gb
|
||||
BIN
graphics/bg.png
Normal file
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
313
rom_builder/rom_builder.py
Normal 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="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("<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])
|
||||
0
rom_builder/roms/- ROM files go here -
Normal file
0
rom_builder/roms/- ROM files go here -
Normal file
320
source/flash.c
Normal file
320
source/flash.c
Normal 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
35
source/flash.h
Normal 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
321
source/font.c
Normal 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
119
source/font.h
Normal 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
288
source/main.c
Normal 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
78
source/main.h
Normal 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
|
||||
Loading…
Reference in New Issue
Block a user