2.81, the "fix OFW and misc bugs" update :P (#5)

* added retry wrapper, made sure SM2 was disabled and not left running

* (hopefully) permanently fix booting OFW. see changelog of 2.81 release for more information
This commit is contained in:
DefenderOfHyrule 2026-03-29 16:39:52 +02:00 committed by GitHub
parent b23720b976
commit 9cce154f9c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 178 additions and 77 deletions

View file

@ -6,65 +6,97 @@
#include "glitch.h" #include "glitch.h"
#include "misc.h" #include "misc.h"
#include "fuses.h" #include "fuses.h"
#include "board_detect.h"
bool mariko = true; bool mariko = true;
bool board_detected = false; bool board_detected = false;
bool wait_for_boot(int timeout_ms) { // maximum number of retries for boot detection before giving up
absolute_time_t tio_full = make_timeout_time_ms(timeout_ms); #define BOOT_DETECT_MAX_RETRIES 4
absolute_time_t tio_cmd1 = tio_full;
init_glitch_pio();
reset_cpu();
uint32_t word=0, last_word=0;
bool was_read_zero = false;
bool was_cmd1 = false;
int reset_attempts = 0;
while(!time_reached(tio_full)) { bool wait_for_boot(int timeout_ms) {
if (time_reached(tio_cmd1)) // retry the entire boot detection sequence up to BOOT_DETECT_MAX_RETRIES times
{ for (int retry = 0; retry < BOOT_DETECT_MAX_RETRIES; retry++) {
if (reset_attempts > 4) absolute_time_t tio_full = make_timeout_time_ms(timeout_ms);
{ absolute_time_t tio_cmd1 = tio_full;
halt_with_error(0, 3); init_glitch_pio();
} reset_cpu();
reset_attempts++; uint32_t word=0, last_word=0;
reset_cpu(); bool was_read_zero = false;
tio_cmd1 = tio_full; bool was_cmd1 = false;
} int reset_attempts = 0;
if(!pio_sm_is_rx_fifo_empty(pio1, 0))
{ while(!time_reached(tio_full)) {
word = pio_sm_get(pio1, 0); if (time_reached(tio_cmd1))
if (last_word == 0x41000000 && word == 0x00F9) // cmd1 request
{
tio_cmd1 = make_timeout_time_ms(20);
was_cmd1 = true;
}
else if (last_word == 0x00F9 && (word >> 24) == 0x3F) // cmd1 responce
{ {
if (reset_attempts > 4)
{
// internal timeout, break and retry the whole sequence
break;
}
reset_attempts++;
reset_cpu();
tio_cmd1 = tio_full; tio_cmd1 = tio_full;
} }
else if (last_word == 0x51000000 && word == 0x0055) //read block 0 if(!pio_sm_is_rx_fifo_empty(pio1, 0))
{ {
tio_full = make_timeout_time_ms(100); word = pio_sm_get(pio1, 0);
was_read_zero = true; if (last_word == 0x41000000 && word == 0x00F9) // cmd1 request
} else if (was_read_zero && last_word == 0x4D000200 && word == 0x00B1) // read status - erista only {
{ tio_cmd1 = make_timeout_time_ms(20);
mariko = false; was_cmd1 = true;
} else if (last_word == 0x51000000 && word == 0x0147) // read block 1, can finish now }
{ else if (last_word == 0x00F9 && (word >> 24) == 0x3F) // cmd1 responce
deinit_glitch_pio(); {
return true; tio_cmd1 = tio_full;
}
else if (last_word == 0x51000000 && word == 0x0055) //read block 0
{
// OLED models sometimes need more time between block 0 and block 1
// original was 100ms, increased to 250ms for (hopefully) better OLED compatibility
tio_full = make_timeout_time_ms(250);
was_read_zero = true;
} else if (was_read_zero && last_word == 0x4D000200 && word == 0x00B1) // read status - erista only
{
mariko = false;
} else if (last_word == 0x51000000 && word == 0x0147) // read block 1, can finish now
{
deinit_glitch_pio();
return true;
}
last_word = word;
} }
last_word = word;
} }
// properly clean up all state machines before retrying
// original deinit only disabled SM 0 and 1, but SM 2 (G_DAT0_SM) was left running
pio_set_sm_mask_enabled(pio1, 0x7, false); // disable SM 0, 1, AND 2 (0x7 = 0b111)
// clean up GPIO pins
for (int i = PIN_CLK; i <= PIN_DAT; i++)
{
gpio_deinit(i);
gpio_disable_pulls(i);
gpio_disable_input_output(i);
}
gpio_deinit(gli_pin());
// only halt with error if all retries are exhausted
if (retry == BOOT_DETECT_MAX_RETRIES - 1) {
if (was_read_zero) {
halt_with_error(1, 3);
}
else if (was_cmd1) {
halt_with_error(2, 3);
} else {
halt_with_error(3, 3);
}
}
// small delay before retrying to let hardware settle
sleep_ms(50);
} }
if (was_read_zero) {
halt_with_error(1, 3);
}
else if (was_cmd1) {
halt_with_error(2, 3);
} else {
halt_with_error(3, 3);
}
return false; return false;
} }

View file

@ -4,7 +4,7 @@
#define OFFSET_MAX 6950 #define OFFSET_MAX 6950
#define VER_HI 2 #define VER_HI 2
#define VER_LO 80 #define VER_LO 81
bool is_configured(); bool is_configured();
void init_config(); void init_config();

View file

@ -65,7 +65,9 @@ void init_glitch_pio() {
} }
void deinit_glitch_pio() { void deinit_glitch_pio() {
pio_set_sm_mask_enabled(pio1, 0x3, false); // original code only disabled SM 0 and 1 (mask 0x3 = 0b011)
// SM 2 (G_DAT0_SM) is also used, so all 3 (mask 0x7 = 0b111) need to be disabled
pio_set_sm_mask_enabled(pio1, 0x7, false);
for (int i = PIN_CLK; i <= PIN_DAT; i++) for (int i = PIN_CLK; i <= PIN_DAT; i++)
{ {
gpio_deinit(i); gpio_deinit(i);

117
payload.c
View file

@ -81,7 +81,7 @@ extern bool mariko;
static inline uint16_t crc_itu_t_byte(uint16_t crc, const uint8_t data) static inline uint16_t crc_itu_t_byte(uint16_t crc, const uint8_t data)
{ {
return (crc << 8) ^ crc_itu_t_table[((crc >> 8) ^ data) & 0xff]; return (crc << 8) ^ crc_itu_t_table[((crc >> 8) ^ data) & 0xff];
} }
uint16_t crc_itu_t(uint16_t crc, const uint8_t *buffer, size_t len) uint16_t crc_itu_t(uint16_t crc, const uint8_t *buffer, size_t len)
@ -91,9 +91,9 @@ uint16_t crc_itu_t(uint16_t crc, const uint8_t *buffer, size_t len)
{ {
crc_prepare_table(); crc_prepare_table();
} }
while (len--) while (len--)
crc = crc_itu_t_byte(crc, *buffer++); crc = crc_itu_t_byte(crc, *buffer++);
return crc; return crc;
} }
extern void zzz(); extern void zzz();
@ -108,18 +108,18 @@ uint16_t payload_crc()
int crc7(uint8_t *buffer, int size) int crc7(uint8_t *buffer, int size)
{ {
uint8_t crc = 0; uint8_t crc = 0;
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
uint8_t c = buffer[i]; uint8_t c = buffer[i];
for (int j = 0; j < 8; j++) { for (int j = 0; j < 8; j++) {
crc <<= 1; crc <<= 1;
if ((crc ^ c) & 0x80) if ((crc ^ c) & 0x80)
crc ^= 9; crc ^= 9;
c <<= 1; c <<= 1;
} }
crc &= 0x7Fu; crc &= 0x7Fu;
} }
return crc; return crc;
} }
void __time_critical_func(cmd_write)(uint8_t cmd, uint32_t argument) void __time_critical_func(cmd_write)(uint8_t cmd, uint32_t argument)
@ -130,9 +130,9 @@ void __time_critical_func(cmd_write)(uint8_t cmd, uint32_t argument)
pio_sm_set_out_pins(pio0, SM_OUT, PIN_CMD, 1); pio_sm_set_out_pins(pio0, SM_OUT, PIN_CMD, 1);
pio_sm_set_enabled(pio0, SM_OUT, true); pio_sm_set_enabled(pio0, SM_OUT, true);
data[0] = cmd | 0x40; data[0] = cmd | 0x40;
*(uint32_t *) &data[1] = __builtin_bswap32(argument); *(uint32_t *) &data[1] = __builtin_bswap32(argument);
data[5] = (crc7(data, 5) << 1) | 1; data[5] = (crc7(data, 5) << 1) | 1;
uint32_t fifo[3]; uint32_t fifo[3];
fifo[0] = 0xFFCF0000 | (data[0] << 8) | data[1]; fifo[0] = 0xFFCF0000 | (data[0] << 8) | data[1];
fifo[1] = __builtin_bswap32(*(uint32_t*)(data + 2)); fifo[1] = __builtin_bswap32(*(uint32_t*)(data + 2));
@ -151,7 +151,7 @@ bool __time_critical_func(dat_write)()
pio_sm_set_out_pins(pio0, SM_OUT, PIN_DAT, 1); pio_sm_set_out_pins(pio0, SM_OUT, PIN_DAT, 1);
pio_sm_set_enabled(pio0, SM_OUT, true); pio_sm_set_enabled(pio0, SM_OUT, true);
uint8_t * buffer = data_buf; uint8_t * buffer = data_buf;
uint16_t crc = crc_itu_t(0, buffer, 512); uint16_t crc = crc_itu_t(0, buffer, 512);
uint32_t words[130]; uint32_t words[130];
int size_bits = 514 * 8 + 2; int size_bits = 514 * 8 + 2;
words[0] = ((size_bits ^ 0xFFFF) << 16) | (buffer[0] << 7) | (buffer[1] >> 1); words[0] = ((size_bits ^ 0xFFFF) << 16) | (buffer[0] << 7) | (buffer[1] >> 1);
@ -413,13 +413,13 @@ uint32_t mmc_init_table[] = {
}; };
bool mmc_initialize() { bool mmc_initialize() {
if (!init_op_cond()) { if (!init_op_cond()) {
return false; return false;
} }
if (!cmd_exec_cid()) { if (!cmd_exec_cid()) {
return false; return false;
} }
for (int i = 0; i < sizeof(mmc_init_table)/sizeof(mmc_init_table[0]); i += 4) for (int i = 0; i < sizeof(mmc_init_table)/sizeof(mmc_init_table[0]); i += 4)
{ {
uint32_t res= 0; uint32_t res= 0;
if (!simple_cmd_exec_with_ret(mmc_init_table[i], mmc_init_table[i+1], &res)) { if (!simple_cmd_exec_with_ret(mmc_init_table[i], mmc_init_table[i+1], &res)) {
@ -495,9 +495,9 @@ extern int boot_slot;
uint8_t temp_buf[512]; uint8_t temp_buf[512];
struct fw_header { struct fw_header {
uint32_t size; uint32_t size;
uint32_t crc; uint32_t crc;
uint8_t data[]; uint8_t data[];
}; };
#define fw_slot_0 ((struct fw_header *) (XIP_BASE + 0x10000)) #define fw_slot_0 ((struct fw_header *) (XIP_BASE + 0x10000))
@ -708,6 +708,69 @@ void prepare_mariko_bct()
memcpy(data_bct + 0x480, mariko_bct_data, 0x2380); memcpy(data_bct + 0x480, mariko_bct_data, 0x2380);
} }
/*
* Atmosphere's fs_mitm (fsmitm_boot0storage.cpp) intercepts all BOOT0 writes and
* drops writes to BctNormalMain (offset 0x0000) and BctSafeMain (offset 0x4000)
* when it detects the modchip's custom public key fill pattern (0x59, 0x69...) in those
* slots. this is by design for AutoRCM preservation, but it means that after a firmware
* update via syscfw, UpdateBootImages successfully writes the new BCT to BctNormalSub (0x8000) and BctSafeSub (0xC000),
* but the writes to BctNormalMain and BctSafeMain are discarded. the result is that Main slots still hold the modchip
* synthetic/fake BCT while Sub slots have the new firmware's real BCT.
*
* when hekate subsequently tries to launch OFW it reads BctNormalMain, which still points
* to the payload area (block 0x1F80) instead of the real pkg1, so OFW fails to boot.
*
* this function detects that diverged state and resyncs by copying Sub > Main for both
* Normal and Safe BCT pairs, restoring them to a consistent state before we overwrite
* Main with the synthetic/fake BCT again on this boot.
*
* detection: read block+1 of each BCT (BCT byte offset 0x220) and compare against the
* modchip magic values:
* erista: 0x69696969 (pubkey fill area spans BCT bytes 0x211-0x30E, 0x220 is within)
* mariko: 0xA56CA203 (first 4 bytes of mariko_bct_sign placed at BCT offset 0x220)
*
* BOOT0 block layout (512-byte blocks):
* BctNormalMain: 0x000-0x01F (BOOT0 byte offset 0x0000, BCT block 0 = BOOT0 block 0x000)
* BctSafeMain: 0x020-0x03F (BOOT0 byte offset 0x4000, BCT block 0 = BOOT0 block 0x020)
* BctNormalSub: 0x040-0x05F (BOOT0 byte offset 0x8000, BCT block 0 = BOOT0 block 0x040)
* BctSafeSub: 0x060-0x07F (BOOT0 byte offset 0xC000, BCT block 0 = BOOT0 block 0x060)
*
* magic is at BCT byte 0x220 = block-relative offset 0x20 within BCT block 1.
* so we read BOOT0 block (bct_start + 1) and check data_buf[0x20].
*/
static bool bct_block_has_modchip_magic(int bct_start_block) {
if (!cmd_mmc_read(bct_start_block + 1) && !cmd_mmc_read(bct_start_block + 1))
return false; // read failure; assume no magic, don't resync
uint32_t magic = *(uint32_t *)(data_buf + 0x20);
return magic == (mariko ? 0xA56CA203 : 0x69696969);
}
void check_and_resync_bct() {
// only relevant on a configured boot where space_bl magic is already present,
// meaning write_payload has previously run and wrote the synthetic/fake BCT.
// if the chip is not yet configured there is nothing to resync.
if (!is_space_bl)
return;
// check Normal BCT pair; Main has magic, Sub does not > Sub has a newer Nintendo BCT
bool normal_main_magic = bct_block_has_modchip_magic(0x000);
bool normal_sub_magic = bct_block_has_modchip_magic(0x040);
if (normal_main_magic && !normal_sub_magic) {
// BctNormalSub was updated by a firmware update but BctNormalMain write was
// dropped by Atmosphere. copy Sub > Main to resync.
copy_bct(0x040, 0x000);
}
// check Safe BCT pair; uses same logic
bool safe_main_magic = bct_block_has_modchip_magic(0x020);
bool safe_sub_magic = bct_block_has_modchip_magic(0x060);
if (safe_main_magic && !safe_sub_magic) {
copy_bct(0x060, 0x020);
}
}
void write_payload() { void write_payload() {
static bool prepared = false; static bool prepared = false;
if (!prepared) if (!prepared)
@ -720,6 +783,10 @@ void write_payload() {
} }
start_mmc(); start_mmc();
reinit_mmc(); reinit_mmc();
// resync BctNormalMain/BctSafeMain from their Sub counterparts if Atmosphere's
// fs_mitm has silently dropped writes to Main during a syscfw firmware update.
// *has to* run before copy_bct backs up Main, so the backup captures the correct state.
check_and_resync_bct();
if (!is_space_bl && !is_command) if (!is_space_bl && !is_command)
{ {
copy_bct(0x0, 0x7A0); copy_bct(0x0, 0x7A0);