diff --git a/shared/bitflags.h b/shared/bitflags.h
index f488bd1..cf3dc11 100644
--- a/shared/bitflags.h
+++ b/shared/bitflags.h
@@ -1,9 +1,38 @@
+/*
+ bitflags.h - definition of bit flags
+
+ Copyright (C) 2025 Retro Rewind Team
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+
#ifndef SHARED_BITFLAGS_H
#define SHARED_BITFLAGS_H
+/*
+ These bitflags are a one-way street - the channel can pass them to the game,
+ but the game cannot set anything to pass back to the channel.
+
+ This is because the routine that loads the channel causes all unused memory to be zeroed.
+ Therefore, for things like crashed or loaded from RR, we cannot use these flags
+ and instead must pass these values around as ephemeral files that the channel checks for
+ and then deletes once read.
+*/
+
#define RRC_BITFLAGS_SAVEGAME (1 << 0)
-#define RRC_BITFLAGS_LOADED_FROM_RR (1 << 1) // set by pulsar
-#define RRC_BITFLAGS_RR_CRASHED (1 << 2) // set by pulsar
+// 1 << 1 and 1 << 2 currently unused
+
#define RRC_BITFLAGS_MY_STUFF_CTGP (1 << 3)
#define RRC_BITFLAGS_MY_STUFF_RR (1 << 4)
#define RRC_BITFLAGS_MY_STUFF_CTGP_MUSIC (1 << 5)
diff --git a/source/crash.c b/source/crash.c
new file mode 100644
index 0000000..b806b70
--- /dev/null
+++ b/source/crash.c
@@ -0,0 +1,81 @@
+
+/*
+ crash.c - crash handler when the main game throws an exception
+
+ Copyright (C) 2025 Retro Rewind Team
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+
+#include "crash.h"
+#include "console.h"
+#include "prompt.h"
+#include "util.h"
+#include "time.h"
+#include "settingsfile.h"
+
+void rrc_crash_handle(void *xfb, struct rrc_settingsfile *settings)
+{
+ char *lines[] = {
+ "- - - Retro Rewind crashed! - - -",
+ "---------------------------------",
+ "This could have been caused by faulty My Stuff",
+ "files, online cheaters, a bug in the pack,",
+ "or a corrupted installation.",
+ };
+ rrc_prompt_1_option(xfb, lines, 5, "Next");
+
+ bool my_stuff_enabled = (settings->my_stuff != RRC_SETTINGSFILE_MY_STUFF_DEFAULT);
+
+ if (my_stuff_enabled)
+ {
+ char *lines2[] = {
+ "- - - Retro Rewind crashed! - - -",
+ "---------------------------------",
+ "It appears that you have My Stuff enabled.",
+ "Before reporting the crash, please try disabling it",
+ "and seeing if the crash still occurs."
+ };
+ rrc_prompt_1_option(xfb, lines2, 5, "OK");
+ }
+ else
+ {
+ char *lines2[] = {
+ "- - - Retro Rewind crashed! - - -",
+ "---------------------------------",
+ "If the crash is consistent, try reinstalling",
+ "Retro Rewind. Make sure to precisely follow the",
+ "instructions found on https://rwfc.net/downloads,",
+ "and do not manually delete any files.",
+ "",
+ "A crash file was written to sd:/RetroRewind6/Crash.pul.",
+ "If you continue to experience issues, please report it",
+ "along with this file to our Discord server:",
+ "https://discord.gg/retrorewind",
+ };
+ rrc_prompt_1_option(xfb, lines2, 11, "OK");
+ }
+}
+
+bool rrc_launched_after_crash()
+{
+ FILE *f = fopen(RRC_CRASH_FILE_PATH, "r");
+ if (f)
+ {
+ fclose(f);
+ remove(RRC_CRASH_FILE_PATH);
+ return true;
+ }
+ return false;
+}
\ No newline at end of file
diff --git a/source/crash.h b/source/crash.h
new file mode 100644
index 0000000..fba63ae
--- /dev/null
+++ b/source/crash.h
@@ -0,0 +1,41 @@
+/*
+ crash.h - crash handler when the main game throws an exception
+
+ Copyright (C) 2025 Retro Rewind Team
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+
+#ifndef RRC_CRASH_H
+#define RRC_CRASH_H
+
+#include "settingsfile.h"
+
+#define RRC_CRASH_FILE_PATH "/RetroRewindChannel/.crash"
+
+/*
+ Run the post-crash handler.
+ Prompts the user about the crash and asks them if they want to upload
+ RetroRewind6/Crash.pul to the servers (if any such file exists).
+ Then clears the flag and returns.
+*/
+void rrc_crash_handle(void *xfb, struct rrc_settingsfile *settings);
+
+/*
+ Checks for the necessary ephemeral file that indicates whether we were launched after a crash.
+ If it exists, deletes it and returns true. Otherwise, returns false.
+*/
+bool rrc_launched_after_crash();
+
+#endif
\ No newline at end of file
diff --git a/source/ephfile.c b/source/ephfile.c
new file mode 100644
index 0000000..53c4a00
--- /dev/null
+++ b/source/ephfile.c
@@ -0,0 +1,35 @@
+/*
+ ephfile.c - Routines for handling of ephemeral files
+
+ Copyright (C) 2025 Retro Rewind Team
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+
+#include
+#include
+
+#include "ephfile.h"
+
+bool rrc_launched_from_rr()
+{
+ FILE *f = fopen(RRC_LAUNCHED_FROM_RR_FILE_PATH, "r");
+ if (f)
+ {
+ fclose(f);
+ remove(RRC_LAUNCHED_FROM_RR_FILE_PATH);
+ return true;
+ }
+ return false;
+}
\ No newline at end of file
diff --git a/source/ephfile.h b/source/ephfile.h
new file mode 100644
index 0000000..8460947
--- /dev/null
+++ b/source/ephfile.h
@@ -0,0 +1,27 @@
+/*
+ ephfile.h - Routines for handling of ephemeral files
+
+ Copyright (C) 2025 Retro Rewind Team
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+
+#ifndef RRC_EPHFILE_H
+#define RRC_EPHFILE_H
+
+#define RRC_LAUNCHED_FROM_RR_FILE_PATH "/RetroRewindChannel/.lfrr"
+
+bool rrc_launched_from_rr();
+
+#endif
\ No newline at end of file
diff --git a/source/loader/loader.c b/source/loader/loader.c
index 50e8423..9bf753c 100644
--- a/source/loader/loader.c
+++ b/source/loader/loader.c
@@ -42,6 +42,11 @@
#include "../settingsfile.h"
#include
+bool rrc_signature_written()
+{
+ return *((u32 *)RRC_SIGNATURE_ADDRESS) == RRC_SIGNATURE_VALUE;
+}
+
/**
* Patches the DVD functions in the game DOL to immediately jump to custom DVD functions implemented in runtime-ext.
* Also allocates trampolines containing the first 4 overwritten instructions + backjump to the original function,
@@ -203,7 +208,7 @@ void rrc_loader_load(struct rrc_dol *dol, struct rrc_settingsfile *settings, voi
DCFlushRange((void *)0x80000000, 0x01800000);
// Signature, used by Pulsar to tell that we've loaded via the new channel instead of Riivolution.
- *(u32 *)RRC_SIGNATURE_ADDRESS = 0xDEADBEEF;
+ *(u32 *)RRC_SIGNATURE_ADDRESS = RRC_SIGNATURE_VALUE;
*(u32 *)RRC_RUNTIME_EXT_ABI_VERSION_ADDRESS = RRC_RUNTIME_EXT_ABI_VERSION;
u8 bitflags = 0;
diff --git a/source/loader/loader.h b/source/loader/loader.h
index a40f821..7aa4899 100644
--- a/source/loader/loader.h
+++ b/source/loader/loader.h
@@ -28,6 +28,10 @@
#define RRC_BI2_SIZE 0x2000
#define RRC_PATCH_COPY_ADDRESS 0x80900000
#define RRC_SIGNATURE_ADDRESS 0x817ffff8
+#define RRC_SIGNATURE_VALUE 0xDEADBEEF
+
+bool rrc_signature_written();
+
#define RRC_RUNTIME_EXT_ABI_VERSION_ADDRESS 0x817ffffc
// Must be kept in sync with runtime-ext/base.ld's PROVIDE and pulsar
#define RRC_RR_BITFLAGS 0x817ffff0
diff --git a/source/main.c b/source/main.c
index c858ef4..1aa128c 100644
--- a/source/main.c
+++ b/source/main.c
@@ -30,6 +30,7 @@
#include
#include
#include
+#include
#include "loader/disc_loader.h"
#include "shutdown.h"
@@ -51,6 +52,8 @@
#include "exception.h"
#include "sd.h"
#include "pad.h"
+#include "crash.h"
+#include "ephfile.h"
/* 100ms */
#define DISKCHECK_DELAY 100000
@@ -104,6 +107,7 @@ int main(int argc, char **argv)
RRC_ASSERTEQ(res, 1, "PAD_Init");
res = WPAD_Init();
RRC_ASSERTEQ(res, WPAD_ERR_NONE, "WPAD_Init");
+
bool first_open = false;
FILE *afd = fopen("sd:/RetroRewindChannel/accept.txt", "r");
@@ -152,6 +156,48 @@ int main(int argc, char **argv)
fclose(afd);
}
+ rrc_con_update("Load settings", 5);
+ struct rrc_settingsfile stored_settings;
+ struct rrc_result settingsfile_res = rrc_settingsfile_parse(&stored_settings);
+ if (rrc_result_is_error(settingsfile_res))
+ {
+ char *lines[] = {
+ rrc_result_strerror(settingsfile_res),
+ (char *)rrc_result_context(settingsfile_res),
+ "It may be possible to fix this by recreating the file.",
+ "Recreate now?",
+ };
+
+ enum rrc_prompt_result prompt_res = rrc_prompt_yes_no(xfb, lines, 4);
+ rrc_result_free(settingsfile_res);
+
+ if (prompt_res == RRC_PROMPT_RESULT_YES)
+ {
+ settingsfile_res = rrc_settingsfile_create();
+ if (rrc_result_is_error(settingsfile_res))
+ {
+ char *lines[] = {
+ "Failed to recreate settings file.",
+ rrc_result_strerror(settingsfile_res),
+ (char *)rrc_result_context(settingsfile_res),
+ "Defaults will be used with no changes on the SD card.",
+ };
+ rrc_prompt_1_option(xfb, lines, 4, "OK");
+ }
+ rrc_result_free(settingsfile_res);
+ }
+
+ // `rrc_settingsfile_parse()` always initializes the settingsfile, so even in case of an error here,
+ // it is initialized with defaults and we can continue with that.
+ }
+
+ bool crashed = false;
+ if(rrc_launched_after_crash())
+ {
+ crashed = true;
+ rrc_crash_handle(xfb, &stored_settings);
+ }
+
rrc_con_update("Initialise DVD", 10);
int fd = rrc_di_init();
RRC_ASSERT(fd != 0, "rrc_di_init");
@@ -196,42 +242,9 @@ int main(int argc, char **argv)
rrc_dbg_printf("FST offset: %d\n", data_header->fst_offset << 2);
rrc_dbg_printf("FST size: %d\n", data_header->fst_size << 2);
- rrc_con_update("Load settings", 20);
- struct rrc_settingsfile stored_settings;
- struct rrc_result settingsfile_res = rrc_settingsfile_parse(&stored_settings);
- if (rrc_result_is_error(settingsfile_res))
- {
- char *lines[] = {
- rrc_result_strerror(settingsfile_res),
- (char *)rrc_result_context(settingsfile_res),
- "It may be possible to fix this by recreating the file.",
- "Recreate now?",
- };
- enum rrc_prompt_result prompt_res = rrc_prompt_yes_no(xfb, lines, 4);
- rrc_result_free(settingsfile_res);
-
- if (prompt_res == RRC_PROMPT_RESULT_YES)
- {
- settingsfile_res = rrc_settingsfile_create();
- if (rrc_result_is_error(settingsfile_res))
- {
- char *lines[] = {
- "Failed to recreate settings file.",
- rrc_result_strerror(settingsfile_res),
- (char *)rrc_result_context(settingsfile_res),
- "Defaults will be used with no changes on the SD card.",
- };
- rrc_prompt_1_option(xfb, lines, 4, "OK");
- }
- rrc_result_free(settingsfile_res);
- }
-
- // `rrc_settingsfile_parse()` always initializes the settingsfile, so even in case of an error here,
- // it is initialized with defaults and we can continue with that.
- }
-
- // Check for updates if the user enabled that setting.
- if (stored_settings.auto_update)
+ bool loaded_from_rr = rrc_launched_from_rr();
+ // Check for updates if the user enabled that setting. Skip if we loaded from the game since this will have already ran.
+ if (stored_settings.auto_update && !loaded_from_rr && !crashed)
{
int update_count;
@@ -262,7 +275,8 @@ int main(int argc, char **argv)
{
break;
}
- else if (rrc_pad_b_pressed(pad) || first_open)
+ // Only allow auto launch if this is a successful first boot
+ else if (rrc_pad_b_pressed(pad) || first_open || loaded_from_rr || crashed)
{
struct rrc_result r;
int out = rrc_settings_display(xfb, &stored_settings, &r);
diff --git a/source/sd.c b/source/sd.c
index 24ca454..7c61847 100644
--- a/source/sd.c
+++ b/source/sd.c
@@ -114,4 +114,17 @@ int rrc_sd_get_folder_file_count(const char *path, struct rrc_result *out_err)
closedir(dir);
return count;
+}
+
+struct rrc_result rrc_sd_get_free_space(unsigned long *res)
+{
+ struct statvfs sbx;
+ int rr = statvfs("/dev/sd", &sbx);
+ if (rr != 0)
+ {
+ return rrc_result_create_error_errno(errno, "Failed to get free space on SD card");
+ }
+
+ *res = sbx.f_bavail * sbx.f_frsize;
+ return rrc_result_success;
}
\ No newline at end of file
diff --git a/source/sd.h b/source/sd.h
index 074c53d..05d332c 100644
--- a/source/sd.h
+++ b/source/sd.h
@@ -47,4 +47,10 @@ bool rrc_sd_folder_exists(const char *path);
Gets the number of files in a folder on the SD card at the given path. The path should be absolute.
*/
int rrc_sd_get_folder_file_count(const char *path, struct rrc_result *out_err);
+
+/**
+ Returns the amount of free space on the sd card in bytes.
+*/
+struct rrc_result rrc_sd_get_free_space(unsigned long *res);
+
#endif
diff --git a/source/update/update.c b/source/update/update.c
index 291e063..fa7b150 100644
--- a/source/update/update.c
+++ b/source/update/update.c
@@ -29,6 +29,7 @@
#include "versionsfile.h"
#include "update.h"
+#include "../sd.h"
#include "../util.h"
#include "../console.h"
#include "../time.h"
@@ -299,7 +300,7 @@ struct rrc_result rrc_update_extract_zip_archive()
}
unsigned long sd_free;
- TRY(sd_get_free_space(&sd_free));
+ TRY(rrc_sd_get_free_space(&sd_free));
if (stat.size > sd_free)
{
@@ -418,7 +419,7 @@ struct rrc_result rrc_update_do_updates_with_state(struct rrc_update_state *stat
}
unsigned long sd_free;
- TRY(sd_get_free_space(&sd_free));
+ TRY(rrc_sd_get_free_space(&sd_free));
if (zipsz > sd_free)
{
diff --git a/source/util.c b/source/util.c
index 74b3725..4da9ec2 100644
--- a/source/util.c
+++ b/source/util.c
@@ -32,19 +32,6 @@ u32 align_up(u32 num, u32 align_as)
return (num + align_as - 1) & -align_as;
}
-struct rrc_result sd_get_free_space(unsigned long *res)
-{
- struct statvfs sbx;
- int rr = statvfs("/dev/sd", &sbx);
- if (rr != 0)
- {
- return rrc_result_create_error_errno(errno, "Failed to get free space on SD card");
- }
-
- *res = sbx.f_bavail * sbx.f_frsize;
- return rrc_result_success;
-}
-
void rrc_invalidate_cache(void *addr, u32 size)
{
// Must be aligned to a 32 byte boundary.
diff --git a/source/util.h b/source/util.h
index bc79c88..ed59a72 100644
--- a/source/util.h
+++ b/source/util.h
@@ -104,9 +104,5 @@ void rrc_invalidate_cache(void *addr, u32 size);
u32 align_down(u32 num, u32 align_as);
u32 align_up(u32 num, u32 align_as);
-/*
- Returns amount of free space on sd card as bytes.
-*/
-struct rrc_result sd_get_free_space(unsigned long *res);
#endif