/* GBA Multi Game Menu Author: Lesserkuma (github.com/lesserkuma) */ #include #include #include #include "main.h" #include "flash.h" u8 flash_type; u8 *itemlist; u16 itemlist_offset; u32 flash_sector_size; u16 flash_buffer_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; flash_buffer_size = 0x400; 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; flash_buffer_size = 0x20; FlashCalcOffsets(); return; } // 1G cart with MSP54LV100S (Zelda Classic Collection 7-in-1) // 2G cart with GL04GR00FHCR2 (369-in-1) _FLASH_WRITE(0, 0xF0); _FLASH_WRITE(0xAAA, 0xA9); _FLASH_WRITE(0x555, 0x56); _FLASH_WRITE(0xAAA, 0x90); data = *(vu32 *)AGB_ROM; _FLASH_WRITE(0, 0xF0); if (data == 0x227D0002) { _FLASH_WRITE(0xAA, 0x98); data = *(vu32 *)(AGB_ROM+0x54) & 0xFFFF; if (((*(vu16 *)(AGB_ROM+0x20) & 0xFF) == 'R') && ((*(vu16 *)(AGB_ROM+0x22) & 0xFF) == 'Q') && ((*(vu16 *)(AGB_ROM+0x24) & 0xFF) == 'Z')) { data = (data & ~3U) | ((data & 1) << 1) | ((data & 2) >> 1); } _FLASH_WRITE(0, 0xF0); REG_IE = ie; flash_type = 3; flash_sector_size = 0x20000; flash_buffer_size = 1 << data; 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); } else if (_flash_type == 3) { _FLASH_WRITE(0xAAA, 0xA9); _FLASH_WRITE(0x555, 0x56); _FLASH_WRITE(0xAAA, 0x80); _FLASH_WRITE(0xAAA, 0xA9); _FLASH_WRITE(0x555, 0x56); _FLASH_WRITE(address, 0x30); while (1) { __asm("nop"); if ((*((vu16 *)(AGB_ROM + address))) == 0xFFFF) { break; } } _FLASH_WRITE(address, 0xF0); } REG_IE = ie; } IWRAM_CODE void FlashWriteData(u32 address, u32 length) { if (flash_type == 0) { FlashDetectType(); } u8 _flash_type = flash_type; u16 _flash_buffer_size = flash_buffer_size; u16 _flash_buffer_count = (_flash_buffer_size >> 1) - 1; 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 / _flash_buffer_size); j++) { _FLASH_WRITE(address + (j * _flash_buffer_size), 0xEA); while (1) { __asm("nop"); if ((p_rom[(j * (_flash_buffer_size >> 1))] & 0x80) == 0x80) { break; } } _FLASH_WRITE(address + (j * _flash_buffer_size), _flash_buffer_count); for (int i = 0; i < _flash_buffer_size; i += 2) { _FLASH_WRITE(address + (j * _flash_buffer_size) + i, data_buffer[(j * _flash_buffer_size) + i + 1] << 8 | data_buffer[(j * _flash_buffer_size) + i]); } _FLASH_WRITE(address + (j * _flash_buffer_size), 0xD0); while (1) { __asm("nop"); if ((p_rom[(j * (_flash_buffer_size >> 1))] & 0x80) == 0x80) { break; } } } _FLASH_WRITE(address, 0xFF); } else if (_flash_type == 2) { for (int j = 0; j < (int)(length / _flash_buffer_size); j++) { _FLASH_WRITE(0xAAA, 0xAAA9); _FLASH_WRITE(0x555, 0x5556); _FLASH_WRITE(address + (j * _flash_buffer_size), 0x2526); _FLASH_WRITE(address + (j * _flash_buffer_size), _flash_buffer_count | (_flash_buffer_count << 8)); u16 data = 0; for (int i = 0; i < _flash_buffer_size; i += 2) { __asm("nop"); data = data_buffer[(j * _flash_buffer_size) + i + 1] << 8 | data_buffer[(j * _flash_buffer_size) + i]; _FLASH_WRITE(address + (j * _flash_buffer_size) + i, data); } _FLASH_WRITE(address + (j * _flash_buffer_size), 0x292A); while (1) { __asm("nop"); if (p_rom[(j * (_flash_buffer_size >> 1)) + ((_flash_buffer_size >> 1) - 1)] == data) { break; } } } _FLASH_WRITE(address, 0xF0F0); } else if (_flash_type == 3) { for (int j = 0; j < (int)(length / _flash_buffer_size); j++) { _FLASH_WRITE(0xAAA, 0xA9); _FLASH_WRITE(0x555, 0x56); _FLASH_WRITE(address + (j * _flash_buffer_size), 0x26); _FLASH_WRITE(address + (j * _flash_buffer_size), _flash_buffer_count); u16 data = 0; for (int i = 0; i < _flash_buffer_size; i += 2) { __asm("nop"); data = data_buffer[(j * _flash_buffer_size) + i + 1] << 8 | data_buffer[(j * _flash_buffer_size) + i]; _FLASH_WRITE(address + (j * _flash_buffer_size) + i, data); } _FLASH_WRITE(address + (j * _flash_buffer_size), 0x2A); while (1) { __asm("nop"); if (p_rom[(j * (_flash_buffer_size >> 1)) + ((_flash_buffer_size >> 1) - 1)] == data) { break; } } } _FLASH_WRITE(address, 0xF0); } 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 located 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 u8 mapper_config1 = ((config.rom_offset / 0x40) & 0xF) << 4; // flash bank (0~7) u8 mapper_config2 = 0x40 + (config.rom_offset % 0x40); // ROM offset (in 512 KB blocks) within current flash bank u8 mapper_config3 = 0x40 - config.rom_size; // accessible ROM size (in 512 KB blocks) u8 mapper_lock = mapper_config2 | 0x80; *(vu8 *)MAPPER_CONFIG1 = mapper_config1; __asm volatile("nop"); *(vu8 *)MAPPER_CONFIG2 = mapper_config2; __asm volatile("nop"); *(vu8 *)MAPPER_CONFIG3 = mapper_config3; __asm volatile("nop"); *(vu8 *)MAPPER_CONFIG2 = mapper_lock; __asm volatile("nop"); // Wait until menu ROM is no longer visible u32 timeout = 0x2FFF; while (((vu16 *)AGB_ROM)[0x58] == 0x4B4C) { if (!timeout--) { REG_IE = 1; REG_BLDY = 0; SystemCall(5); return 2; } } // Lock mapper *(vu8 *)MAPPER_CONFIG2 |= 0x80; // Write buffer to SRAM if (config.save_type != SRAM_NONE) { for (int i = 0; i < SRAM_SIZE; i++) { ((vu8 *)AGB_SRAM)[i] = data_buffer[i]; } } else { *(vu8 *)MAPPER_CONFIG1 = sram_register_backup[0]; *(vu8 *)MAPPER_CONFIG2 = sram_register_backup[1]; *(vu8 *)MAPPER_CONFIG3 = sram_register_backup[2]; *(vu8 *)MAPPER_CONFIG4 = sram_register_backup[3]; } // Clear palette for (int i = 0; i < SCREEN_WIDTH * SCREEN_HEIGHT >> 1; i++) { ((vu16 *)AGB_VRAM)[i] = 0; } REG_BLDY = 0; // Boot ROM __asm("swi 0"); // Soft reset return 3; }