Added ability to display system info

This commit is contained in:
Myria 2017-08-25 02:11:46 +02:00 committed by d0k3
parent 8d4996bc60
commit 99a638a084
6 changed files with 744 additions and 0 deletions

View File

@ -8,6 +8,7 @@
#include <stdlib.h> #include <stdlib.h>
#include <stdio.h> #include <stdio.h>
#include <types.h> #include <types.h>
#include <stdalign.h>
#define max(a,b) \ #define max(a,b) \
(((a) > (b)) ? (a) : (b)) (((a) > (b)) ? (a) : (b))
@ -27,6 +28,11 @@
((((u64) getle32(d+4))<<32) | ((u64) getle32(d))) ((((u64) getle32(d+4))<<32) | ((u64) getle32(d)))
#define align(v,a) \ #define align(v,a) \
(((v) % (a)) ? ((v) + (a) - ((v) % (a))) : (v)) (((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) // GodMode9 / SafeMode9 ("flavor" / splash screen)
#ifndef SAFEMODE #ifndef SAFEMODE

View File

@ -19,3 +19,14 @@
// A9LH + unlocked == SigHax // A9LH + unlocked == SigHax
#define IS_SIGHAX (IS_A9LH && IS_UNLOCKED) #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
};

View File

@ -25,6 +25,7 @@
#include "timer.h" #include "timer.h"
#include "power.h" #include "power.h"
#include "rtc.h" #include "rtc.h"
#include "sysinfo.h"
#include QLZ_SPLASH_H #include QLZ_SPLASH_H
#define N_PANES 2 #define N_PANES 2
@ -1500,6 +1501,7 @@ u32 HomeMoreMenu(char* current_path, DirStruct* current_dir, DirStruct* clipboar
int bsupport = ++n_opt; int bsupport = ++n_opt;
int hsrestore = ((CheckHealthAndSafetyInject("1:") == 0) || (CheckHealthAndSafetyInject("4:") == 0)) ? (int) ++n_opt : -1; int hsrestore = ((CheckHealthAndSafetyInject("1:") == 0) || (CheckHealthAndSafetyInject("4:") == 0)) ? (int) ++n_opt : -1;
int clock = ++n_opt; int clock = ++n_opt;
int sysinfo = ++n_opt;
if (sdformat > 0) optionstr[sdformat - 1] = "SD format menu"; if (sdformat > 0) optionstr[sdformat - 1] = "SD format menu";
if (bonus > 0) optionstr[bonus - 1] = "Bonus drive setup"; 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 (bsupport > 0) optionstr[bsupport - 1] = "Build support files";
if (hsrestore > 0) optionstr[hsrestore - 1] = "Restore H&S"; if (hsrestore > 0) optionstr[hsrestore - 1] = "Restore H&S";
if (clock > 0) optionstr[clock - 1] = "Set RTC date&time"; 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); int user_select = ShowSelectPrompt(n_opt, optionstr, promptstr);
if (user_select == sdformat) { // format SD card if (user_select == sdformat) { // format SD card
@ -1598,6 +1601,15 @@ u32 HomeMoreMenu(char* current_path, DirStruct* current_dir, DirStruct* clipboar
timestr); timestr);
} }
return 0; 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; } else return 1;
return HomeMoreMenu(current_path, current_dir, clipboard); return HomeMoreMenu(current_path, current_dir, clipboard);

171
source/system/itcm.h Normal file
View File

@ -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)

541
source/system/sysinfo.c Normal file
View File

@ -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 <ctype.h>
#include <limits.h>
#include <string.h>
#include <stdarg.h>
#include <stdio.h>
// 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, "<unknown>", 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, "<unknown>", 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, "<unknown>", countof(info->sub_model));
strncpy(info->serial, "<unknown>", countof(info->serial));
strncpy(info->system_region, "<unknown>", countof(info->system_region));
strncpy(info->sales_region, "<unknown>", 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, "<unknown>", countof(info->assembly_date));
strncpy(info->original_firmware, "<unknown>", 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);
}

3
source/system/sysinfo.h Normal file
View File

@ -0,0 +1,3 @@
#pragma once
void MyriaSysinfo(char* sysinfo_txt);