mirror of
https://github.com/d0k3/GodMode9.git
synced 2025-06-26 13:42:47 +00:00
Added ability to display system info
This commit is contained in:
parent
8d4996bc60
commit
99a638a084
@ -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
|
||||||
|
@ -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
|
||||||
|
};
|
||||||
|
@ -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
171
source/system/itcm.h
Normal 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
541
source/system/sysinfo.c
Normal 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
3
source/system/sysinfo.h
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
void MyriaSysinfo(char* sysinfo_txt);
|
Loading…
x
Reference in New Issue
Block a user