diff --git a/sysmodules/rosalina/include/menus/miscellaneous.h b/sysmodules/rosalina/include/menus/miscellaneous.h
index 211a654..638d660 100644
--- a/sysmodules/rosalina/include/menus/miscellaneous.h
+++ b/sysmodules/rosalina/include/menus/miscellaneous.h
@@ -35,3 +35,4 @@ void MiscellaneousMenu_SwitchBoot3dsxTargetTitle(void);
void MiscellaneousMenu_ChangeMenuCombo(void);
void MiscellaneousMenu_SaveSettings(void);
void MiscellaneousMenu_InputRedirection(void);
+void MiscellaneousMenu_SyncTimeDate(void);
\ No newline at end of file
diff --git a/sysmodules/rosalina/include/minisoc.h b/sysmodules/rosalina/include/minisoc.h
index 271ae11..2d006b6 100644
--- a/sysmodules/rosalina/include/minisoc.h
+++ b/sysmodules/rosalina/include/minisoc.h
@@ -33,6 +33,7 @@ int socSocket(int domain, int type, int protocol);
int socBind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
int socListen(int sockfd, int max_connections);
int socAccept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
+int socConnect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
int socPoll(struct pollfd *fds, nfds_t nfds, int timeout);
int socSetsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
int socClose(int sockfd);
diff --git a/sysmodules/rosalina/include/ntp.h b/sysmodules/rosalina/include/ntp.h
new file mode 100644
index 0000000..8780d0d
--- /dev/null
+++ b/sysmodules/rosalina/include/ntp.h
@@ -0,0 +1,33 @@
+/*
+* This file is part of Luma3DS
+* Copyright (C) 2016-2019 Aurora Wright, TuxSH
+*
+* 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 .
+*
+* Additional Terms 7.b and 7.c of GPLv3 apply to this file:
+* * Requiring preservation of specified reasonable legal notices or
+* author attributions in that material or in the Appropriate Legal
+* Notices displayed by works containing it.
+* * Prohibiting misrepresentation of the origin of that material,
+* or requiring that modified versions of such material be marked in
+* reasonable ways as different from the original version.
+*/
+
+#pragma once
+
+#include <3ds/types.h>
+#include
+
+Result ntpGetTimeStamp(time_t *outTimestamp);
+Result ntpSetTimeDate(const struct tm *localt);
\ No newline at end of file
diff --git a/sysmodules/rosalina/source/menus/miscellaneous.c b/sysmodules/rosalina/source/menus/miscellaneous.c
index 82530ff..99052fb 100644
--- a/sysmodules/rosalina/source/menus/miscellaneous.c
+++ b/sysmodules/rosalina/source/menus/miscellaneous.c
@@ -27,6 +27,7 @@
#include <3ds.h>
#include "menus/miscellaneous.h"
#include "input_redirection.h"
+#include "ntp.h"
#include "memory.h"
#include "draw.h"
#include "hbloader.h"
@@ -38,11 +39,12 @@
Menu miscellaneousMenu = {
"Miscellaneous options menu",
- .nbItems = 4,
+ .nbItems = 5,
{
{ "Switch the hb. title to the current app.", METHOD, .method = &MiscellaneousMenu_SwitchBoot3dsxTargetTitle },
- { "Change the menu combo", METHOD, .method = MiscellaneousMenu_ChangeMenuCombo },
+ { "Change the menu combo", METHOD, .method = &MiscellaneousMenu_ChangeMenuCombo },
{ "Start InputRedirection", METHOD, .method = &MiscellaneousMenu_InputRedirection },
+ { "Sync time and date via NTP", METHOD, .method = &MiscellaneousMenu_SyncTimeDate },
{ "Save settings", METHOD, .method = &MiscellaneousMenu_SaveSettings },
}
};
@@ -316,3 +318,85 @@ void MiscellaneousMenu_InputRedirection(void)
}
while(!(waitInput() & BUTTON_B) && !terminationRequest);
}
+
+void MiscellaneousMenu_SyncTimeDate(void)
+{
+ u32 posY;
+ u32 input = 0;
+
+ Result res;
+ bool cantStart = false;
+
+ bool isSocURegistered;
+
+ time_t t;
+ struct tm localt = {0};
+
+ res = srvIsServiceRegistered(&isSocURegistered, "soc:U");
+ cantStart = R_FAILED(res) || !isSocURegistered;
+
+ int utcOffset = 12;
+ int absOffset;
+ do
+ {
+ Draw_Lock();
+ Draw_DrawString(10, 10, COLOR_TITLE, "Miscellaneous options menu");
+
+ //posY = Draw_DrawFormattedString(10, 30, COLOR_WHITE, "Current UTC offset: ");
+ absOffset = utcOffset - 12;
+ absOffset = absOffset < 0 ? -absOffset : absOffset;
+ posY = Draw_DrawFormattedString(10, 30, COLOR_WHITE, "Current UTC offset: %c%02d", utcOffset < 12 ? '-' : '+', absOffset);
+ posY = Draw_DrawFormattedString(10, posY + SPACING_Y, COLOR_WHITE, "Use DPAD Left/Right to change offset.\nPress A when done.") + SPACING_Y;
+
+ input = waitInput();
+
+ if(input & BUTTON_LEFT) utcOffset = (24 + utcOffset - 1) % 24; // ensure utcOffset >= 0
+ if(input & BUTTON_RIGHT) utcOffset = (utcOffset + 1) % 24;
+
+ Draw_FlushFramebuffer();
+ Draw_Unlock();
+ }
+ while(!(input & (BUTTON_A | BUTTON_B)) && !terminationRequest);
+
+ if (input & BUTTON_B)
+ return;
+
+ utcOffset -= 12;
+
+ res = srvIsServiceRegistered(&isSocURegistered, "soc:U");
+ cantStart = R_FAILED(res) || !isSocURegistered;
+ res = 0;
+ if(!cantStart)
+ {
+ res = ntpGetTimeStamp(&t);
+ if(R_SUCCEEDED(res))
+ {
+ t += 3600 * utcOffset;
+ gmtime_r(&t, &localt);
+ res = ntpSetTimeDate(&localt);
+ }
+ }
+
+ do
+ {
+ Draw_Lock();
+ Draw_DrawString(10, 10, COLOR_TITLE, "Miscellaneous options menu");
+
+ absOffset = utcOffset;
+ absOffset = absOffset < 0 ? -absOffset : absOffset;
+ Draw_DrawFormattedString(10, 30, COLOR_WHITE, "Current UTC offset: %c%02d", utcOffset < 0 ? '-' : '+', absOffset);
+ if (cantStart)
+ posY = Draw_DrawFormattedString(10, posY + 2 * SPACING_Y, COLOR_WHITE, "Can't sync time/date before the system\nhas finished loading.") + SPACING_Y;
+ else if (R_FAILED(res))
+ posY = Draw_DrawFormattedString(10, posY + 2 * SPACING_Y, COLOR_WHITE, "Operation failed (%08lx).", (u32)res) + SPACING_Y;
+ else
+ posY = Draw_DrawFormattedString(10, posY + 2 * SPACING_Y, COLOR_WHITE, "Timedate & RTC updated successfully.\nYou may need to reboot to see the changes.") + SPACING_Y;
+
+ input = waitInput();
+
+ Draw_FlushFramebuffer();
+ Draw_Unlock();
+ }
+ while(!(input & BUTTON_B) && !terminationRequest);
+
+}
\ No newline at end of file
diff --git a/sysmodules/rosalina/source/minisoc.c b/sysmodules/rosalina/source/minisoc.c
index ccdc6e8..2b81159 100644
--- a/sysmodules/rosalina/source/minisoc.c
+++ b/sysmodules/rosalina/source/minisoc.c
@@ -312,6 +312,47 @@ int socAccept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
return ret;
}
+int socConnect(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
+{
+ int ret = 0;
+ socklen_t tmp_addrlen = 0;
+ u32 *cmdbuf = getThreadCommandBuffer();
+ u8 tmpaddr[0x1c];
+
+ memset(tmpaddr, 0, 0x1c);
+
+ if(addr->sa_family == AF_INET)
+ tmp_addrlen = 8;
+ else
+ tmp_addrlen = 0x1c;
+
+ if(addrlen < tmp_addrlen)
+ return -1;
+
+ tmpaddr[0] = tmp_addrlen;
+ tmpaddr[1] = addr->sa_family;
+ memcpy(&tmpaddr[2], &addr->sa_data, tmp_addrlen-2);
+
+ cmdbuf[0] = IPC_MakeHeader(0x6,2,4); // 0x60084
+ cmdbuf[1] = (u32)sockfd;
+ cmdbuf[2] = (u32)addrlen;
+ cmdbuf[3] = IPC_Desc_CurProcessId();
+ cmdbuf[5] = IPC_Desc_StaticBuffer(tmp_addrlen,0);
+ cmdbuf[6] = (u32)tmpaddr;
+
+ ret = svcSendSyncRequest(SOCU_handle);
+ if(ret != 0) return -1;
+
+ ret = (int)cmdbuf[1];
+ if(ret == 0)
+ ret = _net_convert_error(cmdbuf[2]);
+
+ if(ret < 0)
+ return -1;
+
+ return 0;
+}
+
int socPoll(struct pollfd *fds, nfds_t nfds, int timeout)
{
int ret = 0;
diff --git a/sysmodules/rosalina/source/ntp.c b/sysmodules/rosalina/source/ntp.c
new file mode 100644
index 0000000..ebdee25
--- /dev/null
+++ b/sysmodules/rosalina/source/ntp.c
@@ -0,0 +1,192 @@
+/*
+* This file is part of Luma3DS
+* Copyright (C) 2016-2019 Aurora Wright, TuxSH
+*
+* 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 .
+*
+* Additional Terms 7.b and 7.c of GPLv3 apply to this file:
+* * Requiring preservation of specified reasonable legal notices or
+* author attributions in that material or in the Appropriate Legal
+* Notices displayed by works containing it.
+* * Prohibiting misrepresentation of the origin of that material,
+* or requiring that modified versions of such material be marked in
+* reasonable ways as different from the original version.
+*/
+
+#include <3ds.h>
+#include
+#include
+#include "utils.h"
+#include "minisoc.h"
+
+#define NUM2BCD(n) ((n<99) ? (((n/10)*0x10)|(n%10)) : 0x99)
+
+#define NTP_TIMESTAMP_DELTA 2208988800ull
+
+typedef struct RtcTime {
+ // From 3dbrew
+ u8 seconds;
+ u8 minutes;
+ u8 hours;
+ u8 dayofweek;
+ u8 dayofmonth;
+ u8 month;
+ u8 year;
+ u8 leapcount;
+} RtcTime;
+
+// From https://github.com/lettier/ntpclient/blob/master/source/c/main.c
+
+typedef struct NtpPacket
+{
+
+ u8 li_vn_mode; // Eight bits. li, vn, and mode.
+ // li. Two bits. Leap indicator.
+ // vn. Three bits. Version number of the protocol.
+ // mode. Three bits. Client will pick mode 3 for client.
+
+ u8 stratum; // Eight bits. Stratum level of the local clock.
+ u8 poll; // Eight bits. Maximum interval between successive messages.
+ u8 precision; // Eight bits. Precision of the local clock.
+
+ u32 rootDelay; // 32 bits. Total round trip delay time.
+ u32 rootDispersion; // 32 bits. Max error aloud from primary clock source.
+ u32 refId; // 32 bits. Reference clock identifier.
+
+ u32 refTm_s; // 32 bits. Reference time-stamp seconds.
+ u32 refTm_f; // 32 bits. Reference time-stamp fraction of a second.
+
+ u32 origTm_s; // 32 bits. Originate time-stamp seconds.
+ u32 origTm_f; // 32 bits. Originate time-stamp fraction of a second.
+
+ u32 rxTm_s; // 32 bits. Received time-stamp seconds.
+ u32 rxTm_f; // 32 bits. Received time-stamp fraction of a second.
+
+ u32 txTm_s; // 32 bits and the most important field the client cares about. Transmit time-stamp seconds.
+ u32 txTm_f; // 32 bits. Transmit time-stamp fraction of a second.
+
+} NtpPacket; // Total: 384 bits or 48 bytes.
+
+void rtcToBcd(u8 *out, const RtcTime *in)
+{
+ memcpy(out, in, 8);
+ for (u32 i = 0; i < 8; i++)
+ {
+ u8 units = out[i] % 10;
+ u8 tens = (out[i] - units) / 10;
+ out[i] = (tens << 4) | units;
+ }
+}
+
+Result ntpGetTimeStamp(time_t *outTimestamp)
+{
+ Result res = 0;
+ struct linger linger;
+ res = miniSocInit();
+ if(R_FAILED(res))
+ return res;
+
+ int sock = socSocket(AF_INET, SOCK_DGRAM, 0);
+ struct sockaddr_in servAddr = {0}; // Server address data structure.
+ NtpPacket packet = {0};
+
+ // Set the first byte's bits to 00,011,011 for li = 0, vn = 3, and mode = 3. The rest will be left set to zero.
+ packet.li_vn_mode = 0x1b;
+
+ // Zero out the server address structure.
+ servAddr.sin_family = AF_INET;
+
+ // Copy the server's IP address to the server address structure.
+
+ servAddr.sin_addr.s_addr = htonl(0xD8EF2300); // 216.239.35.0 time1.google.com
+ // Convert the port number integer to network big-endian style and save it to the server address structure.
+
+ servAddr.sin_port = htons(123);
+
+ // Call up the server using its IP address and port number.
+ res = -1;
+ if(socConnect(sock, (struct sockaddr *)&servAddr, sizeof(struct sockaddr_in)) < 0)
+ goto cleanup;
+
+ if(soc_send(sock, &packet, sizeof(NtpPacket), 0) < 0)
+ goto cleanup;
+
+ if(soc_recv(sock, &packet, sizeof(NtpPacket), 0) < 0)
+ goto cleanup;
+
+ res = 0;
+
+ // These two fields contain the time-stamp seconds as the packet left the NTP server.
+ // The number of seconds correspond to the seconds passed since 1900.
+ // ntohl() converts the bit/byte order from the network's to host's "endianness".
+
+ packet.txTm_s = ntohl(packet.txTm_s); // Time-stamp seconds.
+ packet.txTm_f = ntohl(packet.txTm_f); // Time-stamp fraction of a second.
+
+ // Extract the 32 bits that represent the time-stamp seconds (since NTP epoch) from when the packet left the server.
+ // Subtract 70 years worth of seconds from the seconds since 1900.
+ // This leaves the seconds since the UNIX epoch of 1970.
+ // (1900)------------------(1970)**************************************(Time Packet Left the Server)
+ *outTimestamp = (time_t)(packet.txTm_s - NTP_TIMESTAMP_DELTA);
+
+cleanup:
+ linger.l_onoff = 1;
+ linger.l_linger = 0;
+ socSetsockopt(sock, SOL_SOCKET, SO_LINGER, &linger, sizeof(struct linger));
+
+ socClose(sock);
+ miniSocExit();
+
+ return res;
+}
+
+Result ntpSetTimeDate(const struct tm *localt)
+{
+ Result res = mcuHwcInit();
+ if (R_FAILED(res)) return res;
+
+
+ res = cfguInit();
+ if (R_FAILED(res)) goto cleanup;
+
+ // First, set the config RTC offset to 0
+ u8 rtcOff = 0;
+ res = CFG_SetConfigInfoBlk4(1, 0x10000, &rtcOff);
+ if (R_FAILED(res)) goto cleanup;
+
+ u8 yr = (u8)(localt->tm_year - 100);
+ // Update the RTC
+ u8 bcd[8];
+ RtcTime lt = {
+ .seconds = (u8)localt->tm_sec,
+ .minutes = (u8)localt->tm_min,
+ .hours = (u8)localt->tm_hour,
+ .dayofweek = (u8)localt->tm_wday,
+ .dayofmonth = (u8)localt->tm_mday,
+ .month = (u8)(localt->tm_mon + 1),
+ .year = yr,
+ .leapcount = 0,
+ };
+ rtcToBcd(bcd, <);
+
+ res = MCUHWC_WriteRegister(0x30, bcd, 7);
+ if (R_FAILED(res)) goto cleanup;
+
+ // Save the config changes
+ res = CFG_UpdateConfigSavegame();
+ cleanup:
+ mcuHwcExit();
+ cfguExit();
+ return res;
+}
\ No newline at end of file