diff --git a/source/common/common.h b/source/common/common.h index d7b892c..2c5d8ad 100644 --- a/source/common/common.h +++ b/source/common/common.h @@ -8,6 +8,7 @@ #include #include #include +#include #define max(a,b) \ (((a) > (b)) ? (a) : (b)) @@ -27,6 +28,11 @@ ((((u64) getle32(d+4))<<32) | ((u64) getle32(d))) #define align(v,a) \ (((v) % (a)) ? ((v) + (a) - ((v) % (a))) : (v)) +#define countof(x) \ + (sizeof(x) / sizeof((x)[0])) + +#define STATIC_ASSERT(...) \ + _Static_assert((__VA_ARGS__), #__VA_ARGS__) // GodMode9 / SafeMode9 ("flavor" / splash screen) #ifndef SAFEMODE diff --git a/source/common/unittype.h b/source/common/unittype.h index 37f3f09..9a53881 100644 --- a/source/common/unittype.h +++ b/source/common/unittype.h @@ -19,3 +19,14 @@ // A9LH + unlocked == SigHax #define IS_SIGHAX (IS_A9LH && IS_UNLOCKED) + +// System models +enum SystemModel { + MODEL_OLD_3DS = 0, + MODEL_OLD_3DS_XL, + MODEL_NEW_3DS, + MODEL_OLD_2DS, + MODEL_NEW_3DS_XL, + MODEL_NEW_2DS_XL, + NUM_MODELS +}; diff --git a/source/godmode.c b/source/godmode.c index fc64aa3..f5f6a7e 100644 --- a/source/godmode.c +++ b/source/godmode.c @@ -25,6 +25,7 @@ #include "timer.h" #include "power.h" #include "rtc.h" +#include "sysinfo.h" #include QLZ_SPLASH_H #define N_PANES 2 @@ -1500,6 +1501,7 @@ u32 HomeMoreMenu(char* current_path, DirStruct* current_dir, DirStruct* clipboar int bsupport = ++n_opt; int hsrestore = ((CheckHealthAndSafetyInject("1:") == 0) || (CheckHealthAndSafetyInject("4:") == 0)) ? (int) ++n_opt : -1; int clock = ++n_opt; + int sysinfo = ++n_opt; if (sdformat > 0) optionstr[sdformat - 1] = "SD format menu"; if (bonus > 0) optionstr[bonus - 1] = "Bonus drive setup"; @@ -1507,6 +1509,7 @@ u32 HomeMoreMenu(char* current_path, DirStruct* current_dir, DirStruct* clipboar 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 date&time"; + if (sysinfo > 0) optionstr[sysinfo - 1] = "System info"; int user_select = ShowSelectPrompt(n_opt, optionstr, promptstr); if (user_select == sdformat) { // format SD card @@ -1598,6 +1601,15 @@ u32 HomeMoreMenu(char* current_path, DirStruct* current_dir, DirStruct* clipboar timestr); } return 0; + } else if (user_select == sysinfo) { // Myria's system info + const char* sysinfo_path = OUTPUT_PATH "/sysinfo.txt"; + char* sysinfo_txt = (char*) TEMP_BUFFER; + ShowString("Calculating system info..."); + MyriaSysinfo(sysinfo_txt); + FileSetData(sysinfo_path, sysinfo_txt, strnlen(sysinfo_txt, TEMP_BUFFER_SIZE), 0, true); + FileTextViewer(sysinfo_path); + ShowPrompt(false, "System info written to\n%s", sysinfo_path); + return 0; } else return 1; return HomeMoreMenu(current_path, current_dir, clipboard); diff --git a/source/system/itcm.h b/source/system/itcm.h new file mode 100644 index 0000000..ba20245 --- /dev/null +++ b/source/system/itcm.h @@ -0,0 +1,171 @@ +#pragma once + +// This file contains the structure of ITCM on the ARM9, as initialized by boot9. + +#include "common.h" + + +// Structure of the decrypted version of the OTP. Part of this is in ITCM. +// https://www.3dbrew.org/wiki/OTP_Registers#Plaintext_OTP +typedef struct _Otp { + // 00: Magic value OTP_MAGIC (little-endian). + u32 magic; + // 04: DeviceId, mostly used for TWL stuff. + u32 deviceId; + // 08: Fallback keyY for movable.sed. + u8 fallbackKeyY[16]; + // 18: CtCert flags. When >= 5, ctcertExponent is big-endian? Always 05 in systems I've seen. + u8 ctcertFlags; + // 19: 00 = retail, nonzero (always 02?) = dev + u8 ctcertIssuer; + // 1A-1F: CtCert timestamp - can be used as timestamp of SoC. Year is minus 1900. + u8 timestampYear; + u8 timestampMonth; + u8 timestampDay; + u8 timestampHour; + u8 timestampMinute; + u8 timestampSecond; + // 20: CtCert ECDSA exponent. Big-endian if ctcertFlags >= 5? + u32 ctcertExponent; + // 24: CtCert ECDSA private key, in big-endian. First two bytes always zero? + u8 ctcertPrivK[32]; + // 44: CtCert ECDSA signature, in big-endian. Proves CtCert was made by Nintendo. + u8 ctcertSignature[60]; + // 80: Zero. + u8 zero[16]; + // 90: Random(?) data used for generation of console-specific keys. + u8 random[0x50]; + // E0: SHA-256 hash of the rest of OTP. + u8 hash[256 / 8]; +} __attribute__((__packed__)) Otp; + +// Value of Otp::magic +#define OTP_MAGIC 0xDEADB00F +// Value to add to Otp::timestampYear to get actual year. +#define OTP_YEAR_BIAS 1900 + +// Sanity checking. +STATIC_ASSERT(offsetof(Otp, magic) == 0x00); +STATIC_ASSERT(offsetof(Otp, deviceId) == 0x04); +STATIC_ASSERT(offsetof(Otp, fallbackKeyY) == 0x08); +STATIC_ASSERT(offsetof(Otp, ctcertFlags) == 0x18); +STATIC_ASSERT(offsetof(Otp, ctcertIssuer) == 0x19); +STATIC_ASSERT(offsetof(Otp, timestampYear) == 0x1A); +STATIC_ASSERT(offsetof(Otp, timestampMonth) == 0x1B); +STATIC_ASSERT(offsetof(Otp, timestampDay) == 0x1C); +STATIC_ASSERT(offsetof(Otp, timestampHour) == 0x1D); +STATIC_ASSERT(offsetof(Otp, timestampMinute) == 0x1E); +STATIC_ASSERT(offsetof(Otp, timestampSecond) == 0x1F); +STATIC_ASSERT(offsetof(Otp, ctcertExponent) == 0x20); +STATIC_ASSERT(offsetof(Otp, ctcertPrivK) == 0x24); +STATIC_ASSERT(offsetof(Otp, ctcertSignature) == 0x44); +STATIC_ASSERT(offsetof(Otp, zero) == 0x80); +STATIC_ASSERT(offsetof(Otp, random) == 0x90); +STATIC_ASSERT(offsetof(Otp, hash) == 0xE0); +STATIC_ASSERT(sizeof(Otp) == 256); + + +// Structure of an RSA private key that boot9 puts into ITCM. +// These keys were never used. +typedef struct _Arm9ItcmRsaPrivateKey { + // 000: RSA modulus. + u8 modulus[256]; + // 100: RSA private exponent. + u8 privateExponent[256]; +} __attribute__((__packed__)) Arm9ItcmRsaPrivateKey; + +STATIC_ASSERT(sizeof(Arm9ItcmRsaPrivateKey) == 512); + + +// Structure of the data in ARM9 ITCM, filled in by boot9. +// https://www.3dbrew.org/wiki/OTP_Registers#Plaintext_OTP +typedef struct _Arm9Itcm { + // 0000: Uninitialized / available. + u8 uninitializedBefore[0x3700]; + // 3700: Copied code from boot9 to disable boot9. + u8 boot9DisableCode[0x100]; + // 3800: Decrypted OTP. boot9 zeros last 0x70 bytes (Otp::random and Otp::hash). + Otp otp; + // 3900: Copy of NAND MBR, which includes the NCSD header. + u8 ncsd[0x200]; + // 3B00: Decrypted FIRM header for the FIRM that was loaded by boot9. + u8 decryptedFirmHeader[0x200]; + // 3D00: RSA modulus for keyslots 0-3 as left by boot9 (big-endian). + u8 rsaKeyslotModulus[4][0x100]; + // 4100: RSA private keys for who knows what; nothing ever used them. + Arm9ItcmRsaPrivateKey rsaPrivateKeys[4]; + // 4900: RSA public keys (moduli) for things Nintendo signs. + u8 rsaModulusCartNCSD[0x100]; + u8 rsaModulusAccessDesc[0x100]; + u8 rsaModulusUnused2[0x100]; + u8 rsaModulusUnused3[0x100]; + // 4D00: Unknown keys; unused. + u8 unknownKeys[8][16]; + // 4D80: NAND CID + u8 nandCid[0x64]; + // 4DE4: Padding after NAND CID; uninitialized. + u8 uninitializedCid[0x1C]; + // 4E00: Unused data before TWL keys. + u8 uninitializedMiddle[0x200]; + // 5000: RSA modulus for TWL System Menu. + u8 rsaModulusTwlSystemMenu[0x80]; + // 5080: RSA modulus for TWL Wi-Fi firmware and DSi Sound. + u8 rsaModulusTwlSound[0x80]; + // 5100: RSA modulus for TWL system applications. + u8 rsaModulusTwlSystemApps[0x80]; + // 5180: RSA modulus for TWL normal applications. + u8 rsaModulusTwlNormalApps[0x80]; + // 5200: TWL ??? + u8 twlUnknown1[0x10]; + // 5210: TWL keyY for per-console ES blocks. + u8 twlKeyYUniqueES[0x10]; + // 5220: TWL keyY for fixed-key ES blocks. + u8 twlKeyYFixedES[0x10]; + // 5230: TWL ??? + u8 twlUnknown2[0xD0]; + // 5300: TWL common key. + u8 twlCommonKey[0x10]; + // 5310: TWL ??? + u8 twlUnknown3[0x40]; + // 5350: TWL normal key for keyslot 0x02. + u8 twlKeyslot02NormalKey[0x10]; + // 5360: TWL ??? + u8 twlUnknown4[0x20]; + // 5380: TWL keyX for keyslot 0x00. + u8 twlKeyslot00KeyX[0x10]; + // 5390: TWL ??? + u8 twlUnknown5[8]; + // 5398: TWL "Tad" crypto keyX. + u8 twlTadKeyX[0x10]; + // 53A8: TWL middle two words of keyslot 0x03 keyX. + u8 twlKeyslot03KeyXMiddle[8]; + // 53B0: TWL ??? + u8 twlUnknown6[12]; + // 53BC: TWL keyY for keyslot 0x01. Truncated (last word becomes E1A00005). + u8 twlKeyslot01KeyY[12]; + // 53C8: TWL keyY for NAND crypto on retail TWL. + u8 twlNANDKeyY[0x10]; + // 53D8: TWL ??? + u8 twlUnknown7[8]; + // 53E0: TWL Blowfish cart key. + u8 twlBlowfishCartKey[0x1048]; + // 6428: NTR Blowfish cart key. + u8 ntrBlowfishCartKey[0x1048]; + // 7470: End of the line. + u8 uninitializedEnd1[0x790]; + // 7C00: ...But wait, this buffer is used for the FIRM header during firmlaunch on >= 9.5.0. + u8 firmlaunchHeader[0x100]; + // 7D00: Back to our regularly-scheduled emptiness. + u8 uninitializedEnd2[0x300]; +} __attribute__((__packed__)) Arm9Itcm; + +// Sanity checking. +STATIC_ASSERT(offsetof(Arm9Itcm, otp) == 0x3800); +STATIC_ASSERT(offsetof(Arm9Itcm, twlNANDKeyY) == 0x53C8); +STATIC_ASSERT(offsetof(Arm9Itcm, twlBlowfishCartKey) == 0x53E0); +STATIC_ASSERT(offsetof(Arm9Itcm, ntrBlowfishCartKey) == 0x6428); +STATIC_ASSERT(sizeof(Arm9Itcm) == 0x8000); + + +// Macro for accessing ITCM. +#define ARM9_ITCM ((Arm9Itcm*) 0x01FF8000) diff --git a/source/system/sysinfo.c b/source/system/sysinfo.c new file mode 100644 index 0000000..616620f --- /dev/null +++ b/source/system/sysinfo.c @@ -0,0 +1,541 @@ +#include "common.h" +#include "i2c.h" +#include "itcm.h" +#include "region.h" +#include "unittype.h" +#include "vff.h" +#include "nand/essentials.h" // For SecureInfo +#include +#include +#include +#include +#include + +// Table of system models. +// https://www.3dbrew.org/wiki/Cfg:GetSystemModel#System_Model_Values +static const struct { + char name[12]; + char product_code[4]; +} s_modelNames[] = { + { "Old 3DS", "CTR" }, // 0 + { "Old 3DS XL", "SPR" }, // 1 + { "New 3DS", "KTR" }, // 2 + { "Old 2DS", "FTR" }, // 3 + { "New 3DS XL", "RED" }, // 4 + { "New 2DS XL", "JAN" }, // 5 +}; +STATIC_ASSERT(countof(s_modelNames) == NUM_MODELS); + +// Table of sales regions. +static const struct { + char serial_char; + const char* name; +} s_salesRegions[] = { + // Typical regions. + { 'J', "Japan" }, + { 'W', "Americas" }, // "W" = worldwide? + { 'E', "Europe" }, + { 'C', "China" }, + { 'K', "Korea" }, + { 'T', "Taiwan" }, + // Manufacturing regions that have another region's region lock. + { 'S', "Middle East" }, // "S" = Saudi Arabia? Singapore? (Southeast Asia included.) + { 'A', "Australia" }, +}; + +// Structure of system information. +typedef struct _SysInfo { + // Internal data to pass among these subroutines. + uint8_t int_model; + + // From hardware information. + char model[15 + 1]; + char product_code[3 + 1]; + // From OTP. + char soc_date[19 + 1]; + // From SecureInfo_A/B + char sub_model[15 + 1]; + char serial[15 + 1]; + char system_region[15 + 1]; + char sales_region[15 + 1]; + // From twln: + char assembly_date[19 + 1]; + char original_firmware[15 + 1]; + char preinstalled_titles[16][4]; +} SysInfo; + + +// Read hardware information. +void GetSysInfo_Hardware(SysInfo* info, char nand_drive) { + (void) nand_drive; + + info->int_model = 0xFF; + strncpy(info->model, "", countof(info->model)); + strncpy(info->product_code, "???", countof(info->product_code)); + + // Get MCU system information. + uint8_t mcu_sysinfo[0x13]; + if (I2C_readRegBuf(I2C_DEV_MCU, 0x7F, mcu_sysinfo, sizeof(mcu_sysinfo))) { + // System model. + info->int_model = mcu_sysinfo[0x09]; + if (info->int_model < NUM_MODELS) { + strncpy(info->model, s_modelNames[info->int_model].name, countof(info->model)); + strncpy(info->product_code, s_modelNames[info->int_model].product_code, countof(info->product_code)); + } + } +} + + +// Read OTP. +void GetSysInfo_OTP(SysInfo* info, char nand_drive) { + (void) nand_drive; + + strncpy(info->soc_date, "", countof(info->soc_date)); + + const Otp* otp = &ARM9_ITCM->otp; + + // SoC manufacturing date, we think. + do { + unsigned year = otp->timestampYear + 1900; + + if (year < 2000) + break; + if ((otp->timestampMonth == 0) || (otp->timestampMonth > 12)) + break; + if ((otp->timestampDay == 0) || (otp->timestampDay > 31)) + break; + if (otp->timestampHour >= 24) + break; + if (otp->timestampMinute >= 60) + break; + if (otp->timestampSecond >= 61) + break; + + snprintf(info->soc_date, countof(info->soc_date), "%04u/%02hhu/%02hhu %02hhu:%02hhu:%02hhu", + year, otp->timestampMonth, otp->timestampDay, + otp->timestampHour, otp->timestampMinute, otp->timestampSecond); + } while (false); +} + + +// Read SecureInfo_A. +void GetSysInfo_SecureInfo(SysInfo* info, char nand_drive) { + static char path[] = "_:/rw/sys/SecureInfo__"; + + SecureInfo data; + + path[0] = nand_drive; + + strncpy(info->sub_model, "", countof(info->sub_model)); + strncpy(info->serial, "", countof(info->serial)); + strncpy(info->system_region, "", countof(info->system_region)); + strncpy(info->sales_region, "", countof(info->sales_region)); + + // Try SecureInfo_A then SecureInfo_B. + bool got_data = false; + for (char which = 'A'; which <= 'B'; ++which) { + path[countof(path) - 2] = which; + + UINT got_size; + if (fvx_qread(path, &data, 0, sizeof(data), &got_size) == FR_OK) { + if (got_size == sizeof(data)) { + got_data = true; + break; + } + } + } + + if (!got_data) { + return; + } + + // Decode region. + if (data.region < SMDH_NUM_REGIONS) { + strncpy(info->system_region, g_regionNamesLong[data.region], countof(info->system_region)); + } + + // Retrieve serial number. Set up calculation of check digit. + STATIC_ASSERT(countof(info->serial) > countof(data.serial)); + + bool got_serial = true; + char second_letter = '\0'; + char first_digit = '\0'; + char second_digit = '\0'; + unsigned digits = 0; + unsigned letters = 0; + unsigned odds = 0; + unsigned evens = 0; + + for (unsigned x = 0; x < 15; ++x) { + char ch = data.serial[x]; + info->serial[x] = ch; + + if (ch == '\0') { + break; + } else if ((ch < ' ') || (ch > '~')) { + got_serial = false; + break; + } else if ((ch >= '0') && (ch <= '9')) { + // Track the sum of "odds" and "evens" based on their position. + // The first digit is "odd". + ++digits; + if (digits % 2) + odds += ch - '0'; + else + evens += ch - '0'; + + // Remember the first two digits for submodel check. + if (digits == 1) + first_digit = ch; + else if (digits == 2) + second_digit = ch; + } else { + // Remember the second letter, because that's the sales region. + ++letters; + if (letters == 2) { + second_letter = ch; + } + } + } + + if (!got_serial) { + return; + } + + // Copy the serial out. + strncpy(info->serial, data.serial, countof(data.serial)); + info->serial[countof(data.serial)] = '\0'; + + // Append the check digit if the format appears valid. + size_t length = strlen(info->serial); + if ((length < countof(info->serial) - 1) && (digits == 8)) { + unsigned check_value = 10 - (((3 * evens) + odds) % 10); + char check_digit = (check_value == 10) ? '0' : (char) (check_value + '0'); + + info->serial[length] = check_digit; + info->serial[length + 1] = '\0'; + } + + // Determine the sales region from the second letter of the prefix. + if (second_letter != '\0') { + for (unsigned x = 0; x < countof(s_salesRegions); ++x) { + if (s_salesRegions[x].serial_char == second_letter) { + strncpy(info->sales_region, s_salesRegions[x].name, countof(info->sales_region)); + break; + } + } + } + + // Determine the sub-model from the first two digits of the digit part. + if (first_digit && second_digit) { + if (IS_DEVKIT) { + if ((first_digit == '9') && (second_digit == '0') && (info->int_model == MODEL_OLD_3DS)) { + strncpy(info->sub_model, "Partner-CTR", countof(info->sub_model)); + } else if ((first_digit == '9') && (second_digit == '1') && (info->int_model == MODEL_OLD_3DS)) { + strncpy(info->sub_model, "IS-CTR-BOX", countof(info->sub_model)); + } else if ((first_digit == '9') && (second_digit == '1') && (info->int_model == MODEL_OLD_3DS_XL)) { + strncpy(info->sub_model, "IS-SPR-BOX", countof(info->sub_model)); + } else if ((first_digit == '9') && (second_digit == '1') && (info->int_model == MODEL_NEW_3DS)) { + strncpy(info->sub_model, "IS-SNAKE-BOX", countof(info->sub_model)); + } else { + strncpy(info->sub_model, "panda", countof(info->sub_model)); + } + } else { + if ((first_digit == '0') && (second_digit == '1') && !IS_O3DS) { + strncpy(info->sub_model, "press", countof(info->sub_model)); + } else { + strncpy(info->sub_model, "retail", countof(info->sub_model)); + } + } + } +} + + +// Log file parser helper function. +void SysInfo_ParseText(FIL* file, void (*line_parser)(void*, const char*, size_t), void* context) { + // Buffer we store lines into. + char buffer[512]; + UINT filled = 0; + bool skip_line = false; + bool prev_cr = false; + + // Main loop. + for (;;) { + // How much to read now. + UINT now = (UINT) (sizeof(buffer) - filled); + + // If now is zero, it means that our buffer is full. + if (now == 0) { + // Reset the buffer, but skip this line. + filled = 0; + skip_line = true; + continue; + } + + // Read this chunk. + UINT actual = 0; + if (fvx_read(file, &buffer[filled], now, &actual) != FR_OK) + break; + + // actual == 0 means read past end of file. + if (actual == 0) + break; + + filled += actual; + + // Search for line terminators. + char* current = buffer; + UINT remaining = filled; + for (;;) { + char* carriage = memchr(current, '\r', remaining); + char* newline = memchr(current, '\n', remaining); + + // If neither is present, we have an unfinished line. + if (!carriage && !newline) { + // Move stuff down and return to the outer loop. + memmove(buffer, current, remaining); + filled = remaining; + break; + } + + // We have a complete line, yay. + // Use whichever separator came first. + // Comparing non-null pointers to null is undefined behavior, + // hence the if maze here. + char* found; + if (!carriage) + found = newline; + else if (!newline) + found = carriage; + else + found = min(carriage, newline); + + size_t length = (size_t) (found - current); + + // If this isn't an empty line between a carriage return and + // a linefeed, it's a real line. However, we might need to + // skip lines if they overflow the buffer. + if (!skip_line && ((length != 0) || (found != newline) || !prev_cr)) { + // Report this line. + line_parser(context, current, length); + } + + // Clear the skip_line flag and set prev_cr as needed. + skip_line = false; + prev_cr = (found == carriage); + + // Move on from this line. + remaining -= (found + 1) - current; + current = found + 1; + } + } + + // If we have a partial line at this point, report it as a line. + if (filled > 0) { + line_parser(context, buffer, filled); + } +} + + +// Helper function. +bool SysInfo_IsOnlyDigits(const char* str, size_t length) { + for (; length > 0; ++str, --length) { + if (!isdigit((unsigned char) *str)) { + return false; + } + } + + return true; +} + + +// Split a comma-delimited list. Used for twln:/sys/product.log. +unsigned SysInfo_CommaSplit(const char** starts, size_t* lengths, unsigned max_entries, const char* line, size_t line_length) { + unsigned index = 0; + + if (max_entries == 0) + return 0; + + for (;;) { + // Search for the next comma. + const char* comma = memchr(line, ',', line_length); + + starts[index] = line; + + // If no comma, we're done. + // If we maxed out the entry list, put the rest of the line + // into the last entry. + if (!comma || (index == max_entries - 1)) { + lengths[index] = line_length; + return index + 1; + } + + // Add this entry. + lengths[index] = (size_t) (comma - line); + ++index; + + // Skip over the comma. + line_length -= (size_t) (1 + comma - line); + line = comma + 1; + } +} + + +// Line parser for twln:/sys/log/inspect.log. +void SysInfoLineParser_InspectLog(void* context, const char* line, size_t length) { + SysInfo* info = (SysInfo*) context; + + static const char s_commentUpdated[] = "CommentUpdated="; + + if (length < countof(s_commentUpdated) - 1) + return; + if (memcmp(line, s_commentUpdated, sizeof(s_commentUpdated) - sizeof(s_commentUpdated[0])) != 0) + return; + + length -= countof(s_commentUpdated) - 1; + line += countof(s_commentUpdated) - 1; + length = min(length, countof(info->assembly_date) - 1); + + memcpy(info->assembly_date, line, length * sizeof(*line)); + info->assembly_date[length] = '\0'; +} + + +// Line parser for twln:/sys/log/product.log. +// NOTE: product.log is parsed linearly so that only the last entry in the file +// takes effect. This is important for 3DS's that were reflashed by Nintendo - +// we want whichever information is the latest. +void SysInfoLineParser_ProductLog(void* context, const char* line, size_t length) { + SysInfo* info = (SysInfo*) context; + + const char* entries[10]; + size_t entry_lengths[countof(entries)]; + + unsigned entry_count = SysInfo_CommaSplit(entries, entry_lengths, countof(entries), line, length); + + // Ignore lines that don't have at least 2 entries. + if (entry_count < 2) + return; + + // Ignore lines in which the first entry isn't a number. + if ((entry_lengths[0] == 0) || !SysInfo_IsOnlyDigits(entries[0], entry_lengths[0])) + return; + + // Look for entries we want. + if ((entry_lengths[1] == 8) && (memcmp(entries[1], "DataList", 8) == 0)) { + // Original firmware version is written here. + if ((entry_count < 8) || (entry_lengths[2] != 2) || (memcmp(entries[2], "OK", 2) != 0)) + return; + + // Format: nup:20U cup:9.0.0 preInstall:RA1 + const char* verinfo = entries[7]; + size_t remaining = entry_lengths[7]; + if ((remaining < 4) || (memcmp(verinfo, "nup:", 4) != 0)) + return; + + verinfo += 4; + remaining -= 4; + + // Find whitespace afterward. + const char* nup_start = verinfo; + while ((remaining > 0) && (*verinfo != ' ')) { + ++verinfo; + --remaining; + } + const char* nup_end = verinfo; + + // Skip whitespace until cup:. + while ((remaining > 0) && (*verinfo == ' ')) { + ++verinfo; + --remaining; + } + + if ((remaining < 4) || (memcmp(verinfo, "cup:", 4) != 0)) + return; + + verinfo += 4; + remaining -= 4; + + // Find whitespace afterward. + const char* cup_start = verinfo; + while ((remaining > 0) && (*verinfo != ' ')) { + ++verinfo; + --remaining; + } + const char* cup_end = verinfo; + + // Calculate lengths. + size_t nup_length = (size_t) (nup_end - nup_start); + size_t cup_length = (size_t) (cup_end - cup_start); + + if (nup_length + cup_length < nup_length) + return; + if (nup_length + cup_length > SIZE_MAX - 1 - 1) + return; + + if (cup_length + 1 + nup_length + 1 > countof(info->original_firmware)) + return; + + memcpy(&info->original_firmware[0], cup_start, cup_length); + info->original_firmware[cup_length] = '-'; + memcpy(&info->original_firmware[cup_length + 1], nup_start, nup_length); + info->original_firmware[cup_length + 1 + nup_length] = '\0'; + } +} + + +// Get information from the factory setup log. +void GetSysInfo_TWLN(SysInfo* info, char nand_drive) { + char twln_drive = (char) (nand_drive + 1); + + static char inspect_path[] = " :/sys/log/inspect.log"; + static char product_path[] = " :/sys/log/product.log"; + + inspect_path[0] = twln_drive; + product_path[0] = twln_drive; + + strncpy(info->assembly_date, "", countof(info->assembly_date)); + strncpy(info->original_firmware, "", countof(info->original_firmware)); + + FIL file; + if (fvx_open(&file, inspect_path, FA_READ | FA_OPEN_EXISTING) == FR_OK) { + SysInfo_ParseText(&file, SysInfoLineParser_InspectLog, info); + fvx_close(&file); + } + + if (fvx_open(&file, product_path, FA_READ | FA_OPEN_EXISTING) == FR_OK) { + SysInfo_ParseText(&file, SysInfoLineParser_ProductLog, info); + fvx_close(&file); + } +} + + +void MeowSprintf(char** text, const char* format, ...) +{ + char buffer[256]; + + va_list args; + va_start(args, format); + vsnprintf(*text, countof(buffer), format, args); + va_end(args); + + *text += strlen(*text); +} + + +void MyriaSysinfo(char* sysinfo_txt) { + SysInfo info; + GetSysInfo_Hardware(&info, '1'); + GetSysInfo_OTP(&info, '1'); + GetSysInfo_SecureInfo(&info, '1'); + GetSysInfo_TWLN(&info, '1'); + + char** meow = &sysinfo_txt; + MeowSprintf(meow, "Model: %s (%s)\r\n", info.model, info.sub_model); + MeowSprintf(meow, "Serial: %s\r\n", info.serial); + MeowSprintf(meow, "Region (system): %s\r\n", info.system_region); + MeowSprintf(meow, "Region (sales): %s\r\n", info.sales_region); + MeowSprintf(meow, "SoC manufacturing date: %s\r\n", info.soc_date); + MeowSprintf(meow, "System assembly date: %s\r\n", info.assembly_date); + MeowSprintf(meow, "Original firmware: %s\r\n", info.original_firmware); +} diff --git a/source/system/sysinfo.h b/source/system/sysinfo.h new file mode 100644 index 0000000..5d31cb8 --- /dev/null +++ b/source/system/sysinfo.h @@ -0,0 +1,3 @@ +#pragma once + +void MyriaSysinfo(char* sysinfo_txt);