errdisp: properly handle game medium being removed
- transform some error codes into "NAND damaged" or "Gamecard removed" like official errdisp does - fix bug in SetUserString - add more info to logfile (/luma/errdisp.txt)
This commit is contained in:
parent
635235c86c
commit
19f7ef372b
@ -67,3 +67,4 @@ static inline bool isServiceUsable(const char *name)
|
||||
void formatMemoryPermission(char *outbuf, MemPerm perm);
|
||||
void formatUserMemoryState(char *outbuf, MemState state);
|
||||
u32 formatMemoryMapOfProcess(char *outbuf, u32 bufLen, Handle handle);
|
||||
int dateTimeToString(char *out, u64 msSince1900, bool filenameFormat);
|
||||
|
@ -44,7 +44,7 @@ static MyThread errDispThread;
|
||||
static u8 ALIGN(8) errDispThreadStack[0xD00];
|
||||
|
||||
static char userString[0x100 + 1] = {0};
|
||||
static char staticBuf[0x100 + 1] = {0};
|
||||
static char staticBuf[sizeof(userString)] = {0};
|
||||
|
||||
MyThread *errDispCreateThread(void)
|
||||
{
|
||||
@ -63,27 +63,106 @@ static inline int ERRF_FormatRegisterValue(char *out, const char *name, u32 valu
|
||||
return sprintf(out, "%-9s %08lx", name, value);
|
||||
}
|
||||
|
||||
static inline void ERRF_GetErrInfo(ERRF_FatalErrInfo* info, u32* in, u32 size)
|
||||
static inline void ERRF_PreprocessErrInfo(ERRF_FatalErrInfo *info, u32 *in)
|
||||
{
|
||||
memcpy(info, in, size);
|
||||
memcpy(info, in, sizeof(ERRF_FatalErrInfo));
|
||||
|
||||
Result res = info->resCode;
|
||||
u32 level = R_LEVEL(res);
|
||||
u32 module = R_MODULE(res);
|
||||
u32 summary = R_SUMMARY(res);
|
||||
u32 desc = R_DESCRIPTION(res);
|
||||
|
||||
// ErrDisp has special handling where it expects two errors in a row
|
||||
// for "card removed", but it's fine if we don't.
|
||||
|
||||
if (module != RM_FS)
|
||||
return;
|
||||
|
||||
switch (desc)
|
||||
{
|
||||
// All kinds of "slot1 stuff removed" error ranges
|
||||
case 0x08C ... 0x095:
|
||||
case 0x096 ... 0x0A9:
|
||||
case 0x122 ... 0x12B:
|
||||
case 0x12C ... 0x13F:
|
||||
info->type = ERRF_ERRTYPE_CARD_REMOVED;
|
||||
module = RM_CARD;
|
||||
break;
|
||||
// SDMC removed error range
|
||||
case 0x0AA ... 0x0B3:
|
||||
info->type = ERRF_ERRTYPE_CARD_REMOVED;
|
||||
module = RM_SDMC;
|
||||
break;
|
||||
// Say sike right now
|
||||
case 0x399:
|
||||
info->type = ERRF_ERRTYPE_NAND_DAMAGED;
|
||||
break;
|
||||
}
|
||||
|
||||
info->resCode = MAKERESULT(level, summary, module, desc);
|
||||
}
|
||||
|
||||
static int ERRF_FormatError(char *out, ERRF_FatalErrInfo *info)
|
||||
static int ERRF_FormatRegisterDump(char *out, const ERRF_ExceptionData *exceptionData)
|
||||
{
|
||||
char *out0 = out;
|
||||
static const char *registerNames[] = {
|
||||
"r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11", "r12",
|
||||
"sp", "lr", "pc", "cpsr"
|
||||
};
|
||||
|
||||
u32 *regs = (u32 *)(&exceptionData->regs);
|
||||
for(u32 i = 0; i < 17; i += 2)
|
||||
{
|
||||
out += ERRF_FormatRegisterValue(out, registerNames[i], regs[i]);
|
||||
if(i != 16)
|
||||
{
|
||||
out += sprintf(out, " ");
|
||||
out += ERRF_FormatRegisterValue(out, registerNames[i + 1], i == 16 ? regs[20] : regs[i + 1]);
|
||||
out += sprintf(out, "\n");
|
||||
}
|
||||
}
|
||||
|
||||
if(exceptionData->excep.type == ERRF_EXCEPTION_PREFETCH_ABORT
|
||||
|| exceptionData->excep.type == ERRF_EXCEPTION_DATA_ABORT)
|
||||
{
|
||||
out += sprintf(out, " ");
|
||||
out += ERRF_FormatRegisterValue(out, "far", exceptionData->excep.far);
|
||||
out += sprintf(out, "\n");
|
||||
out += ERRF_FormatRegisterValue(out, "fsr", exceptionData->excep.fsr);
|
||||
}
|
||||
|
||||
else if(exceptionData->excep.type == ERRF_EXCEPTION_VFP)
|
||||
{
|
||||
out += sprintf(out, " ");
|
||||
out += ERRF_FormatRegisterValue(out, "fpexc", exceptionData->excep.fpexc);
|
||||
out += sprintf(out, "\n");
|
||||
out += ERRF_FormatRegisterValue(out, "fpinst", exceptionData->excep.fpinst);
|
||||
out += sprintf(out, " ");
|
||||
out += ERRF_FormatRegisterValue(out, "fpinst2", exceptionData->excep.fpinst2);
|
||||
}
|
||||
return (int)(out - out0);
|
||||
}
|
||||
|
||||
static int ERRF_FormatGenericInfo(char *out, const ERRF_FatalErrInfo *info)
|
||||
{
|
||||
char *outStart = out;
|
||||
static const char *types[] = {
|
||||
"generic", "corrupted", "card removed", "exception", "result failure", "logged", "invalid"
|
||||
"generic", "corrupted", "card removed", "exception", "result failure", "generic (log only)", "invalid"
|
||||
};
|
||||
|
||||
static const char *exceptionTypes[] = {
|
||||
"prefetch abort", "data abort", "undefined instruction", "VFP", "invalid"
|
||||
};
|
||||
|
||||
const char *type = (u32)info->type > (u32)ERRF_ERRTYPE_LOGGED ? types[6] : types[(u32)info->type];
|
||||
const char *type = (u32)info->type > (u32)ERRF_ERRTYPE_LOG_ONLY ? types[6] : types[(u32)info->type];
|
||||
|
||||
char *out0 = out;
|
||||
Handle processHandle;
|
||||
Result res;
|
||||
|
||||
if(info->type == ERRF_ERRTYPE_EXCEPTION)
|
||||
{
|
||||
const char *exceptionType = (u32)info->data.exception_data.excep.type > (u32)ERRF_EXCEPTION_VFP ?
|
||||
const char *exceptionType = (u32) info->data.exception_data.excep.type > (u32)ERRF_EXCEPTION_VFP ?
|
||||
exceptionTypes[4] : exceptionTypes[(u32)info->data.exception_data.excep.type];
|
||||
|
||||
out += sprintf(out, "Error type: exception (%s)\n", exceptionType);
|
||||
@ -91,97 +170,75 @@ static int ERRF_FormatError(char *out, ERRF_FatalErrInfo *info)
|
||||
else
|
||||
out += sprintf(out, "Error type: %s\n", type);
|
||||
|
||||
if(info->type != ERRF_ERRTYPE_CARD_REMOVED)
|
||||
out += sprintf(out, "\nProcess ID: %lu\n", info->procId);
|
||||
|
||||
res = svcOpenProcess(&processHandle, info->procId);
|
||||
if(R_SUCCEEDED(res))
|
||||
{
|
||||
Handle processHandle;
|
||||
Result res;
|
||||
|
||||
out += sprintf(out, "\nProcess ID: %lu\n", info->procId);
|
||||
|
||||
res = svcOpenProcess(&processHandle, info->procId);
|
||||
if(R_SUCCEEDED(res))
|
||||
{
|
||||
u64 titleId;
|
||||
char name[9] = { 0 };
|
||||
svcGetProcessInfo((s64 *)name, processHandle, 0x10000);
|
||||
svcGetProcessInfo((s64 *)&titleId, processHandle, 0x10001);
|
||||
svcCloseHandle(processHandle);
|
||||
out += sprintf(out, "Process name: %s\n", name);
|
||||
out += sprintf(out, "Process title ID: 0x%016llx\n", titleId);
|
||||
}
|
||||
|
||||
out += sprintf(out, "\n");
|
||||
u64 titleId;
|
||||
char name[9] = { 0 };
|
||||
svcGetProcessInfo((s64 *)name, processHandle, 0x10000);
|
||||
svcGetProcessInfo((s64 *)&titleId, processHandle, 0x10001);
|
||||
svcCloseHandle(processHandle);
|
||||
out += sprintf(out, "Process name: %s\n", name);
|
||||
out += sprintf(out, "Process title ID: %016llx\n", titleId);
|
||||
}
|
||||
|
||||
if(info->type == ERRF_ERRTYPE_EXCEPTION)
|
||||
out += sprintf(out, "\n");
|
||||
|
||||
return (int)(out - out0);
|
||||
}
|
||||
|
||||
static int ERRF_FormatError(char *out, const ERRF_FatalErrInfo *info, bool isLogFile)
|
||||
{
|
||||
char *outStart = out;
|
||||
|
||||
if (isLogFile)
|
||||
{
|
||||
static const char *registerNames[] = {
|
||||
"r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11", "r12",
|
||||
"sp", "lr", "pc", "cpsr"
|
||||
};
|
||||
char dateTimeStr[32];
|
||||
u64 timeNow = osGetTime();
|
||||
u64 timeAtBoot = timeNow - (1000 * svcGetSystemTick() / SYSCLOCK_ARM11);
|
||||
dateTimeToString(dateTimeStr, timeNow, false);
|
||||
out += sprintf(out, "Reported on: %s\n", dateTimeStr);
|
||||
dateTimeToString(dateTimeStr, timeAtBoot, false);
|
||||
out += sprintf(out, "System booted on: %s\n\n", dateTimeStr);
|
||||
|
||||
u32 *regs = (u32 *)(&info->data.exception_data.regs);
|
||||
for(u32 i = 0; i < 17; i += 2)
|
||||
{
|
||||
out += ERRF_FormatRegisterValue(out, registerNames[i], regs[i]);
|
||||
if(i != 16)
|
||||
{
|
||||
out += sprintf(out, " ");
|
||||
out += ERRF_FormatRegisterValue(out, registerNames[i + 1], i == 16 ? regs[20] : regs[i + 1]);
|
||||
out += sprintf(out, "\n");
|
||||
}
|
||||
}
|
||||
|
||||
if(info->data.exception_data.excep.type == ERRF_EXCEPTION_PREFETCH_ABORT
|
||||
|| info->data.exception_data.excep.type == ERRF_EXCEPTION_DATA_ABORT)
|
||||
{
|
||||
out += sprintf(out, " ");
|
||||
out += ERRF_FormatRegisterValue(out, "far", info->data.exception_data.excep.far);
|
||||
out += sprintf(out, "\n");
|
||||
out += ERRF_FormatRegisterValue(out, "fsr", info->data.exception_data.excep.fsr);
|
||||
}
|
||||
|
||||
else if(info->data.exception_data.excep.type == ERRF_EXCEPTION_VFP)
|
||||
{
|
||||
out += sprintf(out, " ");
|
||||
out += ERRF_FormatRegisterValue(out, "fpexc", info->data.exception_data.excep.fpexc);
|
||||
out += sprintf(out, "\n");
|
||||
out += ERRF_FormatRegisterValue(out, "fpinst", info->data.exception_data.excep.fpinst);
|
||||
out += sprintf(out, " ");
|
||||
out += ERRF_FormatRegisterValue(out, "fpinst2", info->data.exception_data.excep.fpinst2);
|
||||
out += sprintf(out, "\n");
|
||||
}
|
||||
|
||||
out += sprintf(out, "\n");
|
||||
}
|
||||
else if(info->type != ERRF_ERRTYPE_CARD_REMOVED)
|
||||
switch (info->type)
|
||||
{
|
||||
if(info->type != ERRF_ERRTYPE_FAILURE)
|
||||
out += sprintf(out, "Address: 0x%08lx\n", info->pcAddr);
|
||||
|
||||
out += sprintf(out, "Error code: 0x%08lx\n", info->resCode);
|
||||
}
|
||||
|
||||
const char *desc;
|
||||
switch(info->type)
|
||||
{
|
||||
case ERRF_ERRTYPE_CARD_REMOVED:
|
||||
desc = "The card was removed.";
|
||||
case ERRF_ERRTYPE_NAND_DAMAGED:
|
||||
out += sprintf(out, "The NAND chip has been damaged.\n");
|
||||
break;
|
||||
case ERRF_ERRTYPE_MEM_CORRUPT:
|
||||
desc = "The System Memory has been damaged.";
|
||||
case ERRF_ERRTYPE_CARD_REMOVED:
|
||||
{
|
||||
const char *medium = R_MODULE(info->resCode) == RM_SDMC ? "SD card" : "cartridge";
|
||||
out += sprintf(out, "The %s was removed.\n", medium);
|
||||
break;
|
||||
}
|
||||
case ERRF_ERRTYPE_GENERIC:
|
||||
case ERRF_ERRTYPE_LOG_ONLY:
|
||||
out += ERRF_FormatGenericInfo(out, info);
|
||||
out += sprintf(out, "Address: 0x%08lx\n", info->pcAddr);
|
||||
out += sprintf(out, "Error code: 0x%08lx\n", info->resCode);
|
||||
break;
|
||||
case ERRF_ERRTYPE_EXCEPTION:
|
||||
out += ERRF_FormatGenericInfo(out, info);
|
||||
out += ERRF_FormatRegisterDump(out, &info->data.exception_data);
|
||||
out += sprintf(out, "\n");
|
||||
break;
|
||||
case ERRF_ERRTYPE_FAILURE:
|
||||
info->data.failure_mesg[0x5F] = 0; // make sure the last byte in the IPC buffer is NULL
|
||||
desc = info->data.failure_mesg;
|
||||
out += ERRF_FormatGenericInfo(out, info);
|
||||
out += sprintf(out, "Error code: 0x%08lx\n", info->resCode);
|
||||
out += sprintf(out, "Reason: %.96s\n", info->data.failure_mesg);
|
||||
break;
|
||||
default:
|
||||
desc = "";
|
||||
break;
|
||||
out += sprintf(out, "Invalid fatal error data.\n");
|
||||
}
|
||||
|
||||
if(desc[0] != 0)
|
||||
out += sprintf(out, "\n%s\n", desc);
|
||||
// We might not always have enough space to display this on screen, so keep it to the log file
|
||||
if (isLogFile && userString[0] != '\0')
|
||||
out += sprintf(out, "\n%.256s\n", userString);
|
||||
|
||||
out += sprintf(out, "\n");
|
||||
return out - outStart;
|
||||
}
|
||||
@ -191,10 +248,10 @@ static void ERRF_DisplayError(ERRF_FatalErrInfo *info)
|
||||
{
|
||||
Draw_Lock();
|
||||
|
||||
u32 posY = Draw_DrawString(10, 10, COLOR_RED, userString[0] == 0 ? "An error occurred (ErrDisp)" : userString);
|
||||
u32 posY = Draw_DrawString(10, 10, COLOR_RED, "An error occurred (ErrDisp)");
|
||||
char buf[0x400];
|
||||
|
||||
ERRF_FormatError(buf, info);
|
||||
ERRF_FormatError(buf, info, false);
|
||||
posY = posY < 30 ? 30 : posY;
|
||||
|
||||
posY = Draw_DrawString(10, posY, COLOR_WHITE, buf);
|
||||
@ -216,12 +273,18 @@ static Result ERRF_SaveErrorToFile(ERRF_FatalErrInfo *info)
|
||||
Result res = 0;
|
||||
IFile file;
|
||||
|
||||
n = ERRF_FormatError(buf, info);
|
||||
n += sprintf(buf + n, "-------------------------------------\n\n");
|
||||
|
||||
if(R_FAILED(svcGetSystemInfo(&out, 0x10000, 0x203))) svcBreak(USERBREAK_ASSERT);
|
||||
isSdMode = (bool)out;
|
||||
|
||||
// Bail out early if we know we're unable to write anything, and do not log "SD/cartridge removed" stuff
|
||||
if (info->type == ERRF_ERRTYPE_CARD_REMOVED)
|
||||
return 0;
|
||||
else if (!isSdMode && info->type == ERRF_ERRTYPE_NAND_DAMAGED)
|
||||
return info->resCode;
|
||||
|
||||
n = ERRF_FormatError(buf, info, true);
|
||||
n += sprintf(buf + n, "-------------------------------------\n\n");
|
||||
|
||||
archiveId = isSdMode ? ARCHIVE_SDMC : ARCHIVE_NAND_RW;
|
||||
res = IFile_Open(&file, archiveId, fsMakePath(PATH_EMPTY, ""), fsMakePath(PATH_ASCII, "/luma/errdisp.txt"), FS_OPEN_WRITE | FS_OPEN_CREATE);
|
||||
|
||||
@ -252,9 +315,9 @@ void ERRF_HandleCommands(void)
|
||||
{
|
||||
case 1: // Throw
|
||||
{
|
||||
ERRF_GetErrInfo(&info, (cmdbuf + 1), sizeof(ERRF_FatalErrInfo));
|
||||
ERRF_PreprocessErrInfo(&info, (cmdbuf + 1));
|
||||
ERRF_SaveErrorToFile(&info);
|
||||
if(!menuShouldExit && (info.type != ERRF_ERRTYPE_LOGGED || info.procId == 0))
|
||||
if(!menuShouldExit && info.type != ERRF_ERRTYPE_LOG_ONLY)
|
||||
{
|
||||
menuEnter();
|
||||
|
||||
@ -290,8 +353,9 @@ void ERRF_HandleCommands(void)
|
||||
}
|
||||
else
|
||||
{
|
||||
u32 sz = cmdbuf[1] <= 0x100 ? cmdbuf[1] : 0x100;
|
||||
memcpy(userString, cmdbuf + 3, sz);
|
||||
// Official kernel doesn't copy, but this means it could be overwritten by a rogue command
|
||||
u32 sz = cmdbuf[2] >> 14;
|
||||
memcpy(userString, (const char *)cmdbuf[3], sz);
|
||||
userString[sz] = 0;
|
||||
|
||||
cmdbuf[0] = IPC_MakeHeader(2, 1, 0);
|
||||
@ -299,6 +363,12 @@ void ERRF_HandleCommands(void)
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: // error
|
||||
{
|
||||
cmdbuf[0] = IPC_MakeHeader(0, 1, 0);
|
||||
cmdbuf[1] = 0xD900182F;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -378,6 +378,7 @@ void RosalinaMenu_TakeScreenshot(void)
|
||||
Result res = 0;
|
||||
|
||||
char filename[64];
|
||||
char dateTimeStr[32];
|
||||
|
||||
FS_Archive archive;
|
||||
FS_ArchiveID archiveId;
|
||||
@ -412,64 +413,21 @@ void RosalinaMenu_TakeScreenshot(void)
|
||||
FSUSER_CloseArchive(archive);
|
||||
}
|
||||
|
||||
// Conversion code adapted from https://stackoverflow.com/questions/21593692/convert-unix-timestamp-to-date-without-system-libs
|
||||
// (original author @gnif under CC-BY-SA 4.0)
|
||||
u32 seconds, minutes, hours, days, year, month;
|
||||
u64 milliseconds = osGetTime();
|
||||
seconds = milliseconds/1000;
|
||||
milliseconds %= 1000;
|
||||
minutes = seconds / 60;
|
||||
seconds %= 60;
|
||||
hours = minutes / 60;
|
||||
minutes %= 60;
|
||||
days = hours / 24;
|
||||
hours %= 24;
|
||||
dateTimeToString(dateTimeStr, osGetTime(), true);
|
||||
|
||||
year = 1900; // osGetTime starts in 1900
|
||||
|
||||
while(true)
|
||||
{
|
||||
bool leapYear = (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0));
|
||||
u16 daysInYear = leapYear ? 366 : 365;
|
||||
if(days >= daysInYear)
|
||||
{
|
||||
days -= daysInYear;
|
||||
++year;
|
||||
}
|
||||
else
|
||||
{
|
||||
static const u8 daysInMonth[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
|
||||
for(month = 0; month < 12; ++month)
|
||||
{
|
||||
u8 dim = daysInMonth[month];
|
||||
|
||||
if (month == 1 && leapYear)
|
||||
++dim;
|
||||
|
||||
if (days >= dim)
|
||||
days -= dim;
|
||||
else
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
days++;
|
||||
month++;
|
||||
|
||||
sprintf(filename, "/luma/screenshots/%04lu-%02lu-%02lu_%02lu-%02lu-%02lu.%03llu_top.bmp", year, month, days, hours, minutes, seconds, milliseconds);
|
||||
sprintf(filename, "/luma/screenshots/%s_top.bmp", dateTimeStr);
|
||||
TRY(IFile_Open(&file, archiveId, fsMakePath(PATH_EMPTY, ""), fsMakePath(PATH_ASCII, filename), FS_OPEN_CREATE | FS_OPEN_WRITE));
|
||||
TRY(RosalinaMenu_WriteScreenshot(&file, topWidth, true, true));
|
||||
TRY(IFile_Close(&file));
|
||||
|
||||
sprintf(filename, "/luma/screenshots/%04lu-%02lu-%02lu_%02lu-%02lu-%02lu.%03llu_bot.bmp", year, month, days, hours, minutes, seconds, milliseconds);
|
||||
sprintf(filename, "/luma/screenshots/%s_bot.bmp", dateTimeStr);
|
||||
TRY(IFile_Open(&file, archiveId, fsMakePath(PATH_EMPTY, ""), fsMakePath(PATH_ASCII, filename), FS_OPEN_CREATE | FS_OPEN_WRITE));
|
||||
TRY(RosalinaMenu_WriteScreenshot(&file, bottomWidth, false, true));
|
||||
TRY(IFile_Close(&file));
|
||||
|
||||
if(is3d && (Draw_GetCurrentFramebufferAddress(true, true) != Draw_GetCurrentFramebufferAddress(true, false)))
|
||||
{
|
||||
sprintf(filename, "/luma/screenshots/%04lu-%02lu-%02lu_%02lu-%02lu-%02lu.%03llu_top_right.bmp", year, month, days, hours, minutes, seconds, milliseconds);
|
||||
sprintf(filename, "/luma/screenshots/%s_top_right.bmp", dateTimeStr);
|
||||
TRY(IFile_Open(&file, archiveId, fsMakePath(PATH_EMPTY, ""), fsMakePath(PATH_ASCII, filename), FS_OPEN_CREATE | FS_OPEN_WRITE));
|
||||
TRY(RosalinaMenu_WriteScreenshot(&file, topWidth, true, false));
|
||||
TRY(IFile_Close(&file));
|
||||
|
@ -108,3 +108,56 @@ u32 formatMemoryMapOfProcess(char *outbuf, u32 bufLen, Handle handle)
|
||||
svcCloseHandle(handle);
|
||||
return posInBuffer;
|
||||
}
|
||||
|
||||
int dateTimeToString(char *out, u64 msSince1900, bool filenameFormat)
|
||||
{
|
||||
// Conversion code adapted from https://stackoverflow.com/questions/21593692/convert-unix-timestamp-to-date-without-system-libs
|
||||
// (original author @gnif under CC-BY-SA 4.0)
|
||||
u32 seconds, minutes, hours, days, year, month;
|
||||
u64 milliseconds = msSince1900;
|
||||
seconds = milliseconds/1000;
|
||||
milliseconds %= 1000;
|
||||
minutes = seconds / 60;
|
||||
seconds %= 60;
|
||||
hours = minutes / 60;
|
||||
minutes %= 60;
|
||||
days = hours / 24;
|
||||
hours %= 24;
|
||||
|
||||
year = 1900; // osGetTime starts in 1900
|
||||
|
||||
while(true)
|
||||
{
|
||||
bool leapYear = (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0));
|
||||
u16 daysInYear = leapYear ? 366 : 365;
|
||||
if(days >= daysInYear)
|
||||
{
|
||||
days -= daysInYear;
|
||||
++year;
|
||||
}
|
||||
else
|
||||
{
|
||||
static const u8 daysInMonth[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
|
||||
for(month = 0; month < 12; ++month)
|
||||
{
|
||||
u8 dim = daysInMonth[month];
|
||||
|
||||
if (month == 1 && leapYear)
|
||||
++dim;
|
||||
|
||||
if (days >= dim)
|
||||
days -= dim;
|
||||
else
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
days++;
|
||||
month++;
|
||||
|
||||
if (filenameFormat)
|
||||
return sprintf(out, "%04lu-%02lu-%02lu_%02lu-%02lu-%02lu.%03llu", year, month, days, hours, minutes, seconds, milliseconds);
|
||||
else
|
||||
return sprintf(out, "%04lu-%02lu-%02lu %02lu:%02lu:%02lu", year, month, days, hours, minutes, seconds);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user