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

320
source/flash.c Normal file
View File

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

35
source/flash.h Normal file
View File

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

321
source/font.c Normal file
View File

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

119
source/font.h Normal file
View File

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

288
source/main.c Normal file
View File

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

78
source/main.h Normal file
View File

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