mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-20 04:31:09 +02:00
Fix Switch OTA launcher self-update via a bootstrap NRO.
A running launcher cannot replace its own NRO on sdmc/FAT; stage the new binary and chainload a tiny helper from romfs to swap it in before loading the game.
This commit is contained in:
@@ -130,7 +130,10 @@ ifneq ($(ROMFS),)
|
||||
export NROFLAGS += --romfsdir=$(CURDIR)/$(ROMFS)
|
||||
endif
|
||||
|
||||
.PHONY: $(BUILD) clean all host-test sync-romfs
|
||||
BOOTSTRAP_DIR := $(TOPDIR)/../switch-ota-bootstrap
|
||||
BOOTSTRAP_ROMFS := $(CURDIR)/$(ROMFS)/ota-bootstrap.nro
|
||||
|
||||
.PHONY: $(BUILD) clean all host-test sync-romfs bootstrap-romfs
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
all: sync-romfs $(BUILD)
|
||||
@@ -138,7 +141,12 @@ all: sync-romfs $(BUILD)
|
||||
CACERT_URL := https://curl.se/ca/cacert.pem
|
||||
CACERT_ROMFS := $(CURDIR)/$(ROMFS)/cacert.pem
|
||||
|
||||
sync-romfs:
|
||||
bootstrap-romfs:
|
||||
@$(MAKE) --no-print-directory -C $(BOOTSTRAP_DIR) all
|
||||
@mkdir -p $(CURDIR)/$(ROMFS)
|
||||
@cp -f $(BOOTSTRAP_DIR)/ota-bootstrap.nro $(BOOTSTRAP_ROMFS)
|
||||
|
||||
sync-romfs: bootstrap-romfs
|
||||
@mkdir -p $(CURDIR)/$(ROMFS)
|
||||
@cp -f $(LOGO_SRC) $(CURDIR)/$(ROMFS)/logo.png
|
||||
@if ! curl -sfL --time-cond $(CACERT_ROMFS) -o $(CACERT_ROMFS) $(CACERT_URL); then \
|
||||
|
||||
@@ -68,7 +68,10 @@ Manifest: `scripts/switch/ota_launcher.manifest`.
|
||||
## Status / known gaps
|
||||
|
||||
- Zip extraction uses `switch-zziplib` (`ota_unzip.c`) on device.
|
||||
- OTA replaces launcher + game from the install zip (NACP versions stay aligned).
|
||||
- OTA replaces game + launcher from the install zip (NACP versions stay aligned).
|
||||
The running launcher cannot overwrite its own NRO on sdmc/FAT; a tiny
|
||||
`ota-bootstrap.nro` (embedded in romfs) chainloads once to swap the staged
|
||||
launcher, then loads the game.
|
||||
- HTTPS uses Mozilla CA bundle in romfs (`cacert.pem`, fetched at build time). `ota_net_init()` mounts romfs before the quiet release check.
|
||||
- Sphaira HOME forwarders cache metadata until reinstalled (see docs/switch-install.md).
|
||||
- Release runner: `switch-dev` + (`install_devkitpro_deps.sh` **or** Docker)
|
||||
|
||||
@@ -22,6 +22,12 @@ int ota_fs_atomic_replace_game(const char *install_dir, const char *verified_gam
|
||||
int ota_fs_atomic_replace_nro(const char *install_dir, const char *nro_name,
|
||||
const char *verified_nro, char *err, size_t err_len);
|
||||
|
||||
/* Stage a new launcher + copy romfs bootstrap; chainload bootstrap_out next.
|
||||
* The running launcher cannot replace its own NRO on sdmc/FAT. */
|
||||
int ota_fs_stage_launcher_bootstrap(const char *install_dir, const char *verified_launcher,
|
||||
char *bootstrap_out, size_t bootstrap_out_len, char *err,
|
||||
size_t err_len);
|
||||
|
||||
/* Hand off to game NRO via envSetNextLoad (Switch) or no-op stub (host). */
|
||||
int ota_fs_handoff_to_game(const char *game_nro_path, char *err, size_t err_len);
|
||||
|
||||
|
||||
@@ -12,6 +12,9 @@ extern "C" {
|
||||
#define OTA_LAUNCHER_NRO_NAME "gen1recomp.nro"
|
||||
#define OTA_SAVE_DIR_NAME "pokemon-love2d"
|
||||
#define OTA_INSTALL_DIR "switch/gen1recomp"
|
||||
#define OTA_LAUNCHER_STAGED_SUFFIX ".staged"
|
||||
#define OTA_BOOTSTRAP_ROMFS "romfs:/ota-bootstrap.nro"
|
||||
#define OTA_BOOTSTRAP_SD_NAME "ota-bootstrap.nro"
|
||||
#define OTA_RELEASES_API \
|
||||
"https://api.github.com/repos/bryanthaboi/gen1recomp/releases/latest"
|
||||
|
||||
|
||||
@@ -198,12 +198,15 @@ static int run_update_flow(const char *install_dir) {
|
||||
"Update zip is missing launcher files.", err, installed);
|
||||
return 0;
|
||||
}
|
||||
if (ota_fs_atomic_replace_nro(install_dir, OTA_LAUNCHER_NRO_NAME, extracted_launcher, err,
|
||||
sizeof(err)) != 0) {
|
||||
|
||||
char bootstrap_path[256];
|
||||
bootstrap_path[0] = '\0';
|
||||
if (ota_fs_stage_launcher_bootstrap(install_dir, extracted_launcher, bootstrap_path,
|
||||
sizeof(bootstrap_path), err, sizeof(err)) != 0) {
|
||||
remove(extracted_launcher);
|
||||
remove(zip_path);
|
||||
show_update_error("Could not install",
|
||||
"Game updated but launcher could not be replaced. Reinstall from the SD zip.",
|
||||
"Game updated but launcher could not be staged. Reinstall from the SD zip.",
|
||||
err, installed);
|
||||
return 0;
|
||||
}
|
||||
@@ -217,8 +220,16 @@ static int run_update_flow(const char *install_dir) {
|
||||
fclose(vf);
|
||||
}
|
||||
|
||||
ota_ui_show_progress("Ready", "Starting the game...", 1.0f);
|
||||
ota_ui_show_progress("Ready", "Finishing update...", 1.0f);
|
||||
svcSleepThread(600000000ULL);
|
||||
remove(zip_path);
|
||||
ota_ui_shutdown();
|
||||
|
||||
if (ota_fs_handoff_to_game(bootstrap_path, err, sizeof(err)) != 0) {
|
||||
show_update_error("Could not install", "Launcher bootstrap failed.", err, installed);
|
||||
return 0;
|
||||
}
|
||||
return 2; /* chainload bootstrap; main must not hand off to game */
|
||||
#else
|
||||
(void)extracted;
|
||||
(void)extracted_launcher;
|
||||
@@ -233,18 +244,26 @@ int main(int argc, char **argv) {
|
||||
(void)argv;
|
||||
|
||||
const char *install = SD_INSTALL_DIR;
|
||||
int update_rc = 0;
|
||||
|
||||
#if defined(__SWITCH__)
|
||||
socketInitializeDefault();
|
||||
padConfigureInput(1, HidNpadStyleSet_NpadStandard);
|
||||
if (ota_net_init() == 0) {
|
||||
run_update_flow(install);
|
||||
update_rc = run_update_flow(install);
|
||||
ota_net_shutdown();
|
||||
}
|
||||
#else
|
||||
run_update_flow(install);
|
||||
update_rc = run_update_flow(install);
|
||||
#endif
|
||||
|
||||
if (update_rc == 2) {
|
||||
#if defined(__SWITCH__)
|
||||
socketExit();
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
char game[192];
|
||||
snprintf(game, sizeof(game), "%s/%s", install, OTA_GAME_NRO_NAME);
|
||||
char err[128];
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#if defined(__SWITCH__)
|
||||
#include <switch.h>
|
||||
#include <mbedtls/sha256.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
@@ -114,6 +115,47 @@ int ota_fs_atomic_replace_nro(const char *install_dir, const char *nro_name,
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ota_fs_stage_launcher_bootstrap(const char *install_dir, const char *verified_launcher,
|
||||
char *bootstrap_out, size_t bootstrap_out_len, char *err,
|
||||
size_t err_len) {
|
||||
if (!verified_launcher || !*verified_launcher) {
|
||||
if (err && err_len) snprintf(err, err_len, "missing staged launcher path");
|
||||
return -1;
|
||||
}
|
||||
if (!bootstrap_out || bootstrap_out_len == 0) {
|
||||
if (err && err_len) snprintf(err, err_len, "missing bootstrap output buffer");
|
||||
return -1;
|
||||
}
|
||||
bootstrap_out[0] = '\0';
|
||||
|
||||
const char *base = install_dir ? install_dir : OTA_INSTALL_DIR;
|
||||
char staged[256];
|
||||
char bootstrap[256];
|
||||
char updates[192];
|
||||
snprintf(staged, sizeof(staged), "%s/%s%s", base, OTA_LAUNCHER_NRO_NAME,
|
||||
OTA_LAUNCHER_STAGED_SUFFIX);
|
||||
snprintf(updates, sizeof(updates), "%s/updates", base);
|
||||
snprintf(bootstrap, sizeof(bootstrap), "%s/updates/%s", base, OTA_BOOTSTRAP_SD_NAME);
|
||||
|
||||
remove(staged);
|
||||
if (copy_file(verified_launcher, staged) != 0) {
|
||||
if (err && err_len) snprintf(err, err_len, "stage launcher failed");
|
||||
return -1;
|
||||
}
|
||||
|
||||
#if defined(__SWITCH__)
|
||||
mkdir(updates, 0755);
|
||||
#endif
|
||||
if (copy_file(OTA_BOOTSTRAP_ROMFS, bootstrap) != 0) {
|
||||
remove(staged);
|
||||
if (err && err_len) snprintf(err, err_len, "extract bootstrap failed");
|
||||
return -1;
|
||||
}
|
||||
|
||||
snprintf(bootstrap_out, bootstrap_out_len, "%s", bootstrap);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ota_fs_handoff_to_game(const char *game_nro_path, char *err, size_t err_len) {
|
||||
if (!game_nro_path || !*game_nro_path) {
|
||||
if (err && err_len) snprintf(err, err_len, "missing game path");
|
||||
|
||||
Reference in New Issue
Block a user