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