diff --git a/source/common/common.h b/source/common/common.h index 7062b67..813f737 100644 --- a/source/common/common.h +++ b/source/common/common.h @@ -49,7 +49,7 @@ #endif // GodMode9 version -#define VERSION "1.2.9" +#define VERSION "1.3.0" // input / output paths #define SUPPORT_PATHS "0:/gm9/support", "0:", "0:/files9" // legacy paths diff --git a/source/common/hid.c b/source/common/hid.c index fc3fa95..f74931e 100644 --- a/source/common/hid.c +++ b/source/common/hid.c @@ -2,16 +2,18 @@ #include "i2c.h" #include "timer.h" -u32 InputWait() { +u32 InputWait(u32 timeout_sec) { static u64 delay = 0; u32 pad_state_old = HID_STATE; u32 cart_state_old = CART_STATE; u32 sd_state_old = SD_STATE; - u64 timer_dpad = timer_start(); - u64 timer_mcu = timer_dpad; + u64 timer = timer_start(); + u64 timer_mcu = timer; delay = (delay) ? 72 : 128; while (true) { u32 pad_state = HID_STATE; + if (timeout_sec && (timer_sec(timer) >= timeout_sec)) + return TIMEOUT_HID; // HID timeout if (!(pad_state & BUTTON_ANY)) { // no buttons pressed u32 cart_state = CART_STATE; if (cart_state != cart_state_old) @@ -33,7 +35,7 @@ u32 InputWait() { } if ((pad_state == pad_state_old) && (!(pad_state & BUTTON_ARROW) || - (delay && (timer_msec(timer_dpad) < delay)))) + (delay && (timer_msec(timer) < delay)))) continue; // make sure the key is pressed u32 t_pressed = 0; diff --git a/source/common/hid.h b/source/common/hid.h index 3cb3a2e..e59f41f 100644 --- a/source/common/hid.h +++ b/source/common/hid.h @@ -31,6 +31,7 @@ #define CART_EJECT (1 << 15) #define SD_INSERT (1 << 16) #define SD_EJECT (1 << 17) +#define TIMEOUT_HID (1 << 31) -u32 InputWait(); +u32 InputWait(u32 timeout_sec); bool CheckButton(u32 button); diff --git a/source/common/rtc.c b/source/common/rtc.c new file mode 100644 index 0000000..4b726b3 --- /dev/null +++ b/source/common/rtc.c @@ -0,0 +1,42 @@ +#include "rtc.h" +#include "i2c.h" + +bool is_valid_dstime(DsTime* dstime) { + // check the time... + if ((DSTIMEGET(dstime, bcd_h) >= 24) || + (DSTIMEGET(dstime, bcd_m) >= 60) || + (DSTIMEGET(dstime, bcd_s) >= 60)) + return false; + + // check the date... + u32 year = 2000 + DSTIMEGET(dstime, bcd_Y); + u32 month = DSTIMEGET(dstime, bcd_M); + u32 day = DSTIMEGET(dstime, bcd_D); + + // date: year & month + if ((year >= 2100) || (month == 0) || (month > 12)) + return false; + + // date: day + // see: https://github.com/devkitPro/libnds/blob/9678bf09389cb1fcdc99dfa0357ec0cbe51dd0b7/source/arm7/clock.c#L224-L262 + u32 months_lastday[1+12] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; + u32 leap = (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0)) ? 1 : 0; + u32 days_in_month = months_lastday[month] + ((month == 2) ? leap : 0); + if (day > days_in_month) return false; + + return true; +} + +bool get_dstime(DsTime* dstime) { + return (I2C_readRegBuf(I2C_DEV_MCU, 0x30, (void*) dstime, 7)); +} + +bool set_dstime(DsTime* dstime) { + if (!is_valid_dstime(dstime)) return false; + for (u32 i = 0; i < 7; i++) { + if (i == 3) continue; // skip the unknown byte + if (!I2C_writeReg(I2C_DEV_MCU, 0x30+i, ((u8*)dstime)[i])) + return false; + } + return true; +} diff --git a/source/common/rtc.h b/source/common/rtc.h new file mode 100644 index 0000000..140f9ed --- /dev/null +++ b/source/common/rtc.h @@ -0,0 +1,23 @@ +#pragma once + +#include "common.h" + +#define BCDVALID(b) (((b)<=0x99)&&(((b)&0xF)<=0x9)&&((((b)>>4)&0xF)<=0x9)) +#define BCD2NUM(b) (BCDVALID(b) ? (((b)&0xF)+((((b)>>4)&0xF)*10)) : 0xFF) +#define NUM2BCD(n) ((n<99) ? (((n/10)*0x10)|(n%10)) : 0x99) +#define DSTIMEGET(bcd,n) (BCD2NUM((bcd)->n)) + +// see: http://3dbrew.org/wiki/I2C_Registers#Device_3 (register 30) +typedef struct { + u8 bcd_s; + u8 bcd_m; + u8 bcd_h; + u8 unknown; + u8 bcd_D; + u8 bcd_M; + u8 bcd_Y; +} __attribute__((packed)) DsTime; + +bool is_valid_dstime(DsTime* dstime); +bool get_dstime(DsTime* dstime); +bool set_dstime(DsTime* dstime); diff --git a/source/common/ui.c b/source/common/ui.c index 3b82424..1235b0d 100644 --- a/source/common/ui.c +++ b/source/common/ui.c @@ -9,6 +9,7 @@ #include "font.h" #include "ui.h" +#include "rtc.h" #include "timer.h" #include "hid.h" @@ -266,7 +267,7 @@ bool ShowPrompt(bool ask, const char *format, ...) DrawStringF(MAIN_SCREEN, x, y + str_height - (1*10), COLOR_STD_FONT, COLOR_STD_BG, (ask) ? "( yes, no)" : "( to continue)"); while (true) { - u32 pad_state = InputWait(); + u32 pad_state = InputWait(0); if (pad_state & BUTTON_A) break; else if (pad_state & BUTTON_B) { ret = false; @@ -341,7 +342,7 @@ bool ShowUnlockSequence(u32 seqlvl, const char *format, ...) { } if (lvl == len) break; - u32 pad_state = InputWait(); + u32 pad_state = InputWait(0); if (!(pad_state & BUTTON_ANY)) continue; else if (pad_state & sequences[seqlvl][lvl]) @@ -388,7 +389,7 @@ u32 ShowSelectPrompt(u32 n, const char** options, const char *format, ...) { DrawStringF(MAIN_SCREEN, x, yopt + (12*i), (sel == i) ? COLOR_STD_FONT : COLOR_LIGHTGREY, COLOR_STD_BG, "%2.2s %s", (sel == i) ? "->" : "", options[i]); } - u32 pad_state = InputWait(); + u32 pad_state = InputWait(0); if (pad_state & BUTTON_DOWN) sel = (sel+1) % n; else if (pad_state & BUTTON_UP) sel = (sel+n-1) % n; else if (pad_state & BUTTON_A) break; @@ -460,7 +461,7 @@ bool ShowInputPrompt(char* inputstr, u32 max_size, u32 resize, const char* alpha if (cursor_a < 0) { for (cursor_a = alphabet_size - 1; (cursor_a > 0) && (alphabet[cursor_a] != inputstr[cursor_s]); cursor_a--); } - u32 pad_state = InputWait(); + u32 pad_state = InputWait(0); if (pad_state & BUTTON_A) { ret = true; break; @@ -598,6 +599,75 @@ bool ShowDataPrompt(u8* data, u32* size, const char *format, ...) { return ret; } + +bool ShowRtcSetterPrompt(void* time, const char *format, ...) { + DsTime* dstime = (DsTime*) time; + u32 str_width, str_height; + u32 x, y; + + char str[STRBUF_SIZE] = { 0 }; + va_list va; + va_start(va, format); + vsnprintf(str, STRBUF_SIZE, format, va); + va_end(va); + + // check the initial time + if (!is_valid_dstime(dstime)) { + dstime->bcd_h = dstime->bcd_m = dstime->bcd_s = 0x00; + dstime->bcd_D = dstime->bcd_M = 0x01; + dstime->bcd_Y = 0x00; + } + + str_width = GetDrawStringWidth(str); + str_height = GetDrawStringHeight(str) + (3*10); + if (str_width < (17 * FONT_WIDTH)) str_width = 17 * FONT_WIDTH; + x = (str_width >= SCREEN_WIDTH_MAIN) ? 0 : (SCREEN_WIDTH_MAIN - str_width) / 2; + y = (str_height >= SCREEN_HEIGHT) ? 0 : (SCREEN_HEIGHT - str_height) / 2; + + ClearScreenF(true, false, COLOR_STD_BG); + DrawStringF(MAIN_SCREEN, x, y, COLOR_STD_FONT, COLOR_STD_BG, str); + + int cursor = 0; + bool ret = false; + while (true) { + static const int val_max[] = { 23, 59, 59, 31, 12, 99 }; + static const int val_min[] = { 0, 0, 0, 1, 1, 0 }; + u8* bcd = &(((u8*)dstime)[(cursor<3) ? (2-cursor) : (cursor+1)]); + int val = BCD2NUM(*bcd); + int max = val_max[cursor]; + int min = val_min[cursor]; + DrawStringF(MAIN_SCREEN, x, y + str_height - 18, COLOR_STD_FONT, COLOR_STD_BG, "%02lX:%02lX:%02lX %02lX/%02lX/%02lX\n%-*.*s^^%-*.*s", + (u32) dstime->bcd_h, (u32) dstime->bcd_m, (u32) dstime->bcd_s, (u32) dstime->bcd_D, (u32) dstime->bcd_M, (u32) dstime->bcd_Y, + cursor * 3, cursor * 3, "", 17 - 2 - (cursor * 3), 17 - 2 - (cursor * 3), ""); + + // user input + u32 pad_state = InputWait(0); + if ((pad_state & BUTTON_A) && is_valid_dstime(dstime)) { + ret = true; + break; + } else if (pad_state & BUTTON_B) { + break; + } else if (pad_state & BUTTON_UP) { + val += (pad_state & BUTTON_R1) ? 10 : 1; + if (val > max) val = max; + } else if (pad_state & BUTTON_DOWN) { + val -= (pad_state & BUTTON_R1) ? 10 : 1; + if (val < min) val = min; + } else if (pad_state & BUTTON_LEFT) { + if (--cursor < 0) cursor = 5; + } else if (pad_state & BUTTON_RIGHT) { + if (++cursor > 5) cursor = 0; + } + + // update bcd + *bcd = NUM2BCD(val); + } + + ClearScreenF(true, false, COLOR_STD_BG); + + return ret; +} + bool ShowProgress(u64 current, u64 total, const char* opstr) { static u32 last_prog_width = 0; diff --git a/source/common/ui.h b/source/common/ui.h index e7358f2..40d568a 100644 --- a/source/common/ui.h +++ b/source/common/ui.h @@ -94,4 +94,5 @@ bool ShowStringPrompt(char* inputstr, u32 max_size, const char *format, ...); u64 ShowHexPrompt(u64 start_val, u32 n_digits, const char *format, ...); u64 ShowNumberPrompt(u64 start_val, const char *format, ...); bool ShowDataPrompt(u8* data, u32* size, const char *format, ...); +bool ShowRtcSetterPrompt(void* time, const char *format, ...); bool ShowProgress(u64 current, u64 total, const char* opstr); diff --git a/source/fatfs/diskio.c b/source/fatfs/diskio.c index 7b47612..3d245db 100644 --- a/source/fatfs/diskio.c +++ b/source/fatfs/diskio.c @@ -12,6 +12,7 @@ #include "ramdrive.h" #include "nand.h" #include "sdmmc.h" +#include "rtc.h" #define FREE_MIN_SECTORS 0x2000 // minimum sectors for the free drive to appear (4MB) @@ -65,6 +66,26 @@ static BYTE imgnand_mode = 0x00; +/*-----------------------------------------------------------------------*/ +/* Get current FAT time */ +/*-----------------------------------------------------------------------*/ + +DWORD get_fattime( void ) { + DsTime dstime; + get_dstime(&dstime); + DWORD fattime = + ((DSTIMEGET(&dstime, bcd_s)&0x3F) >> 1 ) | + ((DSTIMEGET(&dstime, bcd_m)&0x3F) << 5 ) | + ((DSTIMEGET(&dstime, bcd_h)&0x3F) << 11) | + ((DSTIMEGET(&dstime, bcd_D)&0x1F) << 16) | + ((DSTIMEGET(&dstime, bcd_M)&0x0F) << 21) | + (((DSTIMEGET(&dstime, bcd_Y)+(2000-1980))&0x7F) << 25); + + return fattime; +} + + + /*-----------------------------------------------------------------------*/ /* Get Drive Status */ /*-----------------------------------------------------------------------*/ diff --git a/source/fatfs/diskio.h b/source/fatfs/diskio.h index 945f391..6acc84c 100644 --- a/source/fatfs/diskio.h +++ b/source/fatfs/diskio.h @@ -32,6 +32,7 @@ typedef enum { /* Prototypes for disk control functions */ +DWORD get_fattime( void ); // not a disk control function, but fits here DSTATUS disk_initialize (BYTE pdrv); DSTATUS disk_status (BYTE pdrv); DRESULT disk_read (BYTE pdrv, BYTE* buff, DWORD sector, UINT count); diff --git a/source/fatfs/ffconf.h b/source/fatfs/ffconf.h index 08ffab0..4d70f2b 100644 --- a/source/fatfs/ffconf.h +++ b/source/fatfs/ffconf.h @@ -215,7 +215,7 @@ / Note that enabling exFAT discards ANSI C (C89) compatibility. */ -#define _FS_NORTC 1 +#define _FS_NORTC 0 #define _NORTC_MON 1 #define _NORTC_MDAY 1 #define _NORTC_YEAR 2017 diff --git a/source/game/gameutil.c b/source/game/gameutil.c index 42ef26a..c53edcc 100644 --- a/source/game/gameutil.c +++ b/source/game/gameutil.c @@ -1444,7 +1444,7 @@ u32 ShowSmdhTitleInfo(Smdh* smdh) { WordWrapString(desc_s, lwrap); WordWrapString(pub, lwrap); ShowIconString(icon, SMDH_DIM_ICON_BIG, SMDH_DIM_ICON_BIG, "%s\n%s\n%s", desc_l, desc_s, pub); - InputWait(); + InputWait(0); ClearScreenF(true, false, COLOR_STD_BG); return 0; } @@ -1460,7 +1460,7 @@ u32 ShowNdsFileTitleInfo(const char* path) { return 1; WordWrapString(desc, lwrap); ShowIconString(icon, TWLICON_DIM_ICON, TWLICON_DIM_ICON, "%s", desc); - InputWait(); + InputWait(0); ClearScreenF(true, false, COLOR_STD_BG); return 0; diff --git a/source/godmode.c b/source/godmode.c index 1ab875f..e4a36d4 100644 --- a/source/godmode.c +++ b/source/godmode.c @@ -24,6 +24,7 @@ #include "qlzcomp.h" #include "timer.h" #include "power.h" +#include "rtc.h" #include "i2c.h" #include QLZ_SPLASH_H @@ -124,6 +125,17 @@ static inline char* LineSeek(const char* text, u32 len, u32 ww, const char* line } } +void GetTimeString(char* timestr, bool forced_update) { + static DsTime dstime; + static u64 timer = (u64) -1; // this ensures we don't check the time too often + if (forced_update || (timer == (u64) -1) || (timer_sec(timer) > 30)) { + get_dstime(&dstime); + timer = timer_start(); + } + if (timestr) snprintf(timestr, 31, "%02lX/%02lX/%02lX %02lX:%02lX", + (u32) dstime.bcd_D, (u32) dstime.bcd_M, (u32) dstime.bcd_Y, (u32) dstime.bcd_h, (u32) dstime.bcd_m); +} + void DrawUserInterface(const char* curr_path, DirEntry* curr_entry, DirStruct* clipboard, u32 curr_pane) { const u32 n_cb_show = 8; const u32 bartxt_start = (FONT_HEIGHT_EXT == 10) ? 1 : 2; @@ -162,8 +174,10 @@ void DrawUserInterface(const char* curr_path, DirEntry* curr_entry, DirStruct* c snprintf(tempstr, 64, "%s/%s", bytestr0, bytestr1); DrawStringF(TOP_SCREEN, bartxt_rx, bartxt_start, COLOR_STD_BG, COLOR_TOP_BAR, "%19.19s", tempstr); } else { + char timestr[32]; + GetTimeString(timestr, false); DrawStringF(TOP_SCREEN, bartxt_x, bartxt_start, COLOR_STD_BG, COLOR_TOP_BAR, "[root]"); - DrawStringF(TOP_SCREEN, bartxt_rx, bartxt_start, COLOR_STD_BG, COLOR_TOP_BAR, "%19.19s", FLAVOR); + DrawStringF(TOP_SCREEN, bartxt_rx, bartxt_start, COLOR_STD_BG, COLOR_TOP_BAR, "%19.19s", timestr); } // left top - current file info @@ -434,7 +448,7 @@ u32 FileTextViewer(const char* path) { } // handle user input - u32 pad_state = InputWait(); + u32 pad_state = InputWait(0); if ((pad_state & BUTTON_R1) && (pad_state & BUTTON_L1)) CreateScreenshot(); else { // standard viewer mode char* line0_next = line0; @@ -646,7 +660,7 @@ u32 FileHexViewer(const char* path) { } // handle user input - u32 pad_state = InputWait(); + u32 pad_state = InputWait(0); if ((pad_state & BUTTON_R1) && (pad_state & BUTTON_L1)) CreateScreenshot(); else if (!edit_mode) { // standard viewer mode u32 step_ud = (pad_state & BUTTON_R1) ? (0x1000 - (0x1000 % cols)) : cols; @@ -1458,6 +1472,7 @@ u32 HomeMoreMenu(char* current_path, DirStruct* current_dir, DirStruct* clipboar int multi = (CheckMultiEmuNand()) ? (int) ++n_opt : -1; int bsupport = ++n_opt; int hsrestore = ((CheckHealthAndSafetyInject("1:") == 0) || (CheckHealthAndSafetyInject("4:") == 0)) ? (int) ++n_opt : -1; + int clock = ++n_opt; int scripts = (PathExist(SCRIPT_PATH)) ? (int) ++n_opt : -1; if (sdformat > 0) optionstr[sdformat - 1] = "SD format menu"; @@ -1465,6 +1480,7 @@ u32 HomeMoreMenu(char* current_path, DirStruct* current_dir, DirStruct* clipboar if (multi > 0) optionstr[multi - 1] = "Switch EmuNAND"; if (bsupport > 0) optionstr[bsupport - 1] = "Build support files"; if (hsrestore > 0) optionstr[hsrestore - 1] = "Restore H&S"; + if (clock > 0) optionstr[clock - 1] = "Set RTC clock"; if (scripts > 0) optionstr[scripts - 1] = "Scripts..."; int user_select = ShowSelectPrompt(n_opt, optionstr, promptstr); @@ -1546,6 +1562,17 @@ u32 HomeMoreMenu(char* current_path, DirStruct* current_dir, DirStruct* clipboar GetDirContents(current_dir, current_path); return 0; } + } else if (user_select == clock) { // RTC clock setter + DsTime dstime; + get_dstime(&dstime); + if (ShowRtcSetterPrompt(&dstime, "Set RTC time/date")) { + char timestr[32]; + set_dstime(&dstime); + GetTimeString(timestr, true); + ShowPrompt(false, "New RTC time/date is:\n%s\n \nHint: HOMEMENU time needs\nmanual adjustment after\nsetting the RTC.", + timestr); + } + return 0; } else if (user_select == scripts) { // scripts menu char script[256]; if (FileSelector(script, "HOME scripts... menu.\nSelect action:", SCRIPT_PATH, "*.gm9", true, false)) { @@ -1665,7 +1692,7 @@ u32 GodMode() { } // handle user input - u32 pad_state = InputWait(); + u32 pad_state = InputWait(30); bool switched = (pad_state & BUTTON_R1); // basic navigation commands