From 24d3a0ae66d22915dab5b43ac82a8f3b72963efd Mon Sep 17 00:00:00 2001 From: Metroid Maniac Date: Sun, 1 Jan 2023 07:10:42 +0000 Subject: [PATCH] Dump GBA RTC data in metadata and saves (#205) * Add barebones RTC (actually GPIO) detect * RTC reading gotta clean up * Nicer printing * Add date sanity check (detect RTC presence) * Append RTC data to dumped saves mGBA-style if present * Actually allow restoring save with RTC included --- arm9/source/date.cpp | 24 ++++--- arm9/source/date.h | 12 ++-- arm9/source/dumpOperations.cpp | 19 +++++- arm9/source/file_browse.cpp | 3 +- arm9/source/gba.cpp | 115 ++++++++++++++++++++++++++++++++- arm9/source/gba.h | 13 ++++ 6 files changed, 160 insertions(+), 26 deletions(-) diff --git a/arm9/source/date.cpp b/arm9/source/date.cpp index cb58238..348e3cb 100644 --- a/arm9/source/date.cpp +++ b/arm9/source/date.cpp @@ -6,24 +6,22 @@ #include #include -/** - * Get the current time formatted for the top bar. - * @return std::string containing the time. - */ -std::string RetTime() -{ - return RetTime(STR_TIME_FORMAT.c_str()); -} - /** * Get the current time formatted as specified. * @return std::string containing the time. */ -std::string RetTime(const char *format) +std::string RetTime(const char *format, time_t *raw) { - time_t raw; - time(&raw); - const struct tm *Time = localtime(&raw); + if (!format) + { + format = STR_TIME_FORMAT.c_str(); + } + time_t systime; + if (!raw) { + raw = &systime; + time(raw); + } + const struct tm *Time = localtime(raw); char tmp[64]; strftime(tmp, sizeof(tmp), format, Time); diff --git a/arm9/source/date.h b/arm9/source/date.h index 6e497b9..d2f037e 100644 --- a/arm9/source/date.h +++ b/arm9/source/date.h @@ -4,15 +4,11 @@ #include /** - * Get the current time formatted for the top bar. + * Format the time as specified. + * If no format is specified, use format for the top bar. + * If no time is specified, use the system time. * @return std::string containing the time. */ -std::string RetTime(); - -/** - * Get the current time formatted as specified. - * @return std::string containing the time. - */ -std::string RetTime(const char *format); +std::string RetTime(const char *format = nullptr, time_t *raw = nullptr); #endif // DATE_H diff --git a/arm9/source/dumpOperations.cpp b/arm9/source/dumpOperations.cpp index 3ce9bae..d3ca0f8 100644 --- a/arm9/source/dumpOperations.cpp +++ b/arm9/source/dumpOperations.cpp @@ -964,6 +964,14 @@ void gbaCartSaveDump(const char *filename) { FILE *destinationFile = fopen(filename, "wb"); fwrite(buffer, 1, size, destinationFile); + + u8 cartRtc[RTC_SIZE]; + if (gbaGetRtc(cartRtc)) { + fwrite(cartRtc, 1, RTC_SIZE, destinationFile); + u64 systime = time(nullptr); + fwrite(&systime, 1, 8, destinationFile); + } + fclose(destinationFile); delete[] buffer; } @@ -996,7 +1004,7 @@ void gbaCartSaveRestore(const char *filename) { fseek(sourceFile, 0, SEEK_END); size_t length = ftell(sourceFile); fseek(sourceFile, 0, SEEK_SET); - if(length != size) { + if(length != size && length != size + 16) { fclose(sourceFile); dumpFailMsg(STR_SAVE_SIZE_MISMATCH_CART); @@ -1254,6 +1262,15 @@ void gbaCartDump(void) { if(saveType == SAVE_GBA_FLASH_64 || saveType == SAVE_GBA_FLASH_128) fprintf(destinationFile, "Save chip ID : 0x%04X\n", gbaGetFlashId()); + u8 cartRtc[RTC_SIZE]; + if (gbaGetRtc(cartRtc)) { + struct tm cartTm = gbaRtcToTm(cartRtc); + time_t cartTime = mktime(&cartTm); + fprintf(destinationFile, + "Cart time : %s\n", + RetTime("%Y-%m-%d %H:%M:%S", &cartTime).c_str()); + } + fprintf(destinationFile, "Timestamp : %s\n" "GM9i Version : " VER_NUMBER "\n", diff --git a/arm9/source/file_browse.cpp b/arm9/source/file_browse.cpp index bc39700..09cb453 100644 --- a/arm9/source/file_browse.cpp +++ b/arm9/source/file_browse.cpp @@ -196,7 +196,8 @@ FileOperation fileBrowse_A(DirEntry* entry, char path[PATH_MAX]) { if(extension(entry->name, {"sav", "sav1", "sav2", "sav3", "sav4", "sav5", "sav6", "sav7", "sav8", "sav9"})) { if(!(io_dldi_data->ioInterface.features & FEATURE_SLOT_NDS) || entry->size <= (1 << 20)) operations.push_back(FileOperation::restoreSaveNds); - if(isRegularDS && (entry->size == 512 || entry->size == 8192 || entry->size == 32768 || entry->size == 65536 || entry->size == 131072)) + if(isRegularDS && (entry->size == 512 || entry->size == 8192 || entry->size == 32768 || entry->size == 65536 || entry->size == 131072 + || entry->size == 528 || entry->size == 8208 || entry->size == 32784 || entry->size == 65552 || entry->size == 131088)) operations.push_back(FileOperation::restoreSaveGba); } if(currentDrive != Drive::fatImg && extension(entry->name, {"img", "sd", "sav", "pub", "pu1", "pu2", "pu3", "pu4", "pu5", "pu6", "pu7", "pu8", "pu9", "prv", "pr1", "pr2", "pr3", "pr4", "pr5", "pr6", "pr7", "pr8", "pr9", "0000"})) { diff --git a/arm9/source/gba.cpp b/arm9/source/gba.cpp index 00a2c33..d103632 100644 --- a/arm9/source/gba.cpp +++ b/arm9/source/gba.cpp @@ -45,8 +45,6 @@ inline u32 min(u32 i, u32 j) { return (i < j) ? i : j;} inline u32 max(u32 i, u32 j) { return (i > j) ? i : j;} - - // ----------------------------------------------------- #define MAGIC_EEPR 0x52504545 #define MAGIC_SRAM 0x4d415253 @@ -54,7 +52,6 @@ inline u32 max(u32 i, u32 j) { return (i > j) ? i : j;} #define MAGIC_H1M_ 0x5f4d3148 - // ----------------------------------------------------------- bool gbaIsGame() { @@ -380,3 +377,115 @@ bool gbaFormatSave(saveTypeGBA type) } return true; } + +#define GPIO_DAT (*(vu16*) 0x080000c4) +#define GPIO_DIR (*(vu16*) 0x080000c6) +#define GPIO_CNT (*(vu16*) 0x080000c8) + +#define RTC_CMD_READ(x) (((x)<<1) | 0x61) +#define RTC_CMD_WRITE(x) (((x)<<1) | 0x60) + +static void rtcEnable() +{ + GPIO_CNT = 1; +} + +static void rtcDisable() +{ + GPIO_CNT = 0; +} + +static void rtcWriteCmd(u8 cmd) +{ + int l; + u16 b; + u16 v = cmd <<1; + for(l=7; l>=0; l--) + { + b = (v>>l) & 0x2; + GPIO_DAT = b | 4; + GPIO_DAT = b | 4; + GPIO_DAT = b | 4; + GPIO_DAT = b | 5; + } +} + +static void rtcWriteData(u8 data) +{ + int l; + u16 b; + u16 v = data <<1; + for(l=0; l<8; l++) + { + b = (v>>l) & 0x2; + GPIO_DAT = b | 4; + GPIO_DAT = b | 4; + GPIO_DAT = b | 4; + GPIO_DAT = b | 5; + } +} +static u8 rtcReadData() +{ + int j,l; + u16 b; + int v = 0; + for(l=0; l<8; l++) + { + for(j=0;j<5; j++) + GPIO_DAT = 4; + GPIO_DAT = 5; + b = GPIO_DAT; + v = v | ((b & 2)<>1; + return v; +} + +bool gbaGetRtc(u8 *rtc) +{ + rtcEnable(); + + int i; + GPIO_DAT = 1; + GPIO_DIR = 7; + GPIO_DAT = 1; + GPIO_DAT = 5; + rtcWriteCmd(RTC_CMD_READ(2)); + GPIO_DIR = 5; + for(i=0; i<4; i++) + rtc[i] = rtcReadData(); + GPIO_DIR = 5; + for(i=4; i<7; i++) + rtc[i] = rtcReadData(); + + GPIO_DAT = 1; + GPIO_DIR = 7; + GPIO_DAT = 1; + GPIO_DAT = 5; + rtcWriteCmd(RTC_CMD_READ(4)); + GPIO_DIR = 5; + rtc[7] = rtcReadData(); + + rtcDisable(); + + // Month must be 1 to 12 in BCD for valid RTC + // If month is 0, invalid RTC + return rtc[RTC_MONTH] >= 0x01 && rtc[RTC_MONTH] <= 0x12; +} + +static uint8_t unBCD(uint8_t byte) { + return (byte >> 4) * 10 + (byte & 0xF); +} + +struct tm gbaRtcToTm(const u8 *rtc) +{ + struct tm res; + res.tm_year = unBCD(rtc[RTC_YEAR]) + 100; + res.tm_mon = unBCD(rtc[RTC_MONTH]) - 1; + res.tm_mday = unBCD(rtc[RTC_DAY]); + res.tm_hour = unBCD(rtc[RTC_HOUR]); + res.tm_min = unBCD(rtc[RTC_MINUTE]); + res.tm_sec = unBCD(rtc[RTC_SECOND]); + res.tm_isdst = -1; + return res; +} \ No newline at end of file diff --git a/arm9/source/gba.h b/arm9/source/gba.h index 6a22901..5a61070 100644 --- a/arm9/source/gba.h +++ b/arm9/source/gba.h @@ -34,6 +34,17 @@ enum saveTypeGBA { SAVE_GBA_FLASH_128 // 128k }; +enum GbaRtc { + RTC_YEAR, + RTC_MONTH, + RTC_DAY, + RTC_WEEKDAY, + RTC_HOUR, + RTC_MINUTE, + RTC_SECOND, + RTC_CONTROL, + RTC_SIZE +}; // -------------------- bool gbaIsGame(); @@ -46,5 +57,7 @@ bool gbaReadSave(u8 *dst, u32 src, u32 len, saveTypeGBA type); bool gbaWriteSave(u32 dst, u8 *src, u32 len, saveTypeGBA type); bool gbaFormatSave(saveTypeGBA type); +bool gbaGetRtc(u8 *rtc); +struct tm gbaRtcToTm(const u8 *rtc); #endif // __GBA_H__ \ No newline at end of file