Implement working prodinfo blanking.

This commit is contained in:
Michael Scire 2019-05-23 03:10:32 -07:00
parent 3bba035b84
commit 967f14fc7e
13 changed files with 239 additions and 82 deletions

View file

@ -282,7 +282,11 @@ uint32_t configitem_get(bool privileged, ConfigItem item, uint64_t *p_outvalue)
break; break;
case CONFIGITEM_HAS_RCM_BUG_PATCH: case CONFIGITEM_HAS_RCM_BUG_PATCH:
/* UNOFFICIAL: Gets whether this unit has the RCM bug patched. */ /* UNOFFICIAL: Gets whether this unit has the RCM bug patched. */
*p_outvalue = (int)(fuse_has_rcm_bug_patch());; *p_outvalue = (int)(fuse_has_rcm_bug_patch());
break;
case CONFIGITEM_SHOULD_BLANK_PROD_INFO:
/* UNOFFICIAL: Gets whether we should blank out certain parts of PRODINFO. */
*p_outvalue = (int)(exosphere_should_blank_prod_info() != 0);
break; break;
default: default:
result = 2; result = 2;

View file

@ -45,6 +45,7 @@ typedef enum {
CONFIGITEM_NEEDS_SHUTDOWN = 65002, CONFIGITEM_NEEDS_SHUTDOWN = 65002,
CONFIGITEM_EXOSPHERE_VERHASH = 65003, CONFIGITEM_EXOSPHERE_VERHASH = 65003,
CONFIGITEM_HAS_RCM_BUG_PATCH = 65004, CONFIGITEM_HAS_RCM_BUG_PATCH = 65004,
CONFIGITEM_SHOULD_BLANK_PROD_INFO = 65005,
} ConfigItem; } ConfigItem;
#define REBOOT_KIND_NO_REBOOT 0 #define REBOOT_KIND_NO_REBOOT 0

View file

@ -83,3 +83,11 @@ unsigned int exosphere_should_disable_usermode_exception_handlers(void) {
return EXOSPHERE_CHECK_FLAG(EXOSPHERE_FLAG_DISABLE_USERMODE_EXCEPTION_HANDLERS); return EXOSPHERE_CHECK_FLAG(EXOSPHERE_FLAG_DISABLE_USERMODE_EXCEPTION_HANDLERS);
} }
unsigned int exosphere_should_blank_prod_info(void) {
if (!g_has_loaded_config) {
generic_panic();
}
return EXOSPHERE_CHECK_FLAG(EXOSPHERE_FLAG_SHOULD_BLANK_PRODINFO);
}

View file

@ -39,6 +39,7 @@
#define EXOSPHERE_FLAG_IS_DEBUGMODE_PRIV (1 << 1u) #define EXOSPHERE_FLAG_IS_DEBUGMODE_PRIV (1 << 1u)
#define EXOSPHERE_FLAG_IS_DEBUGMODE_USER (1 << 2u) #define EXOSPHERE_FLAG_IS_DEBUGMODE_USER (1 << 2u)
#define EXOSPHERE_FLAG_DISABLE_USERMODE_EXCEPTION_HANDLERS (1 << 3u) #define EXOSPHERE_FLAG_DISABLE_USERMODE_EXCEPTION_HANDLERS (1 << 3u)
#define EXOSPHERE_FLAG_SHOULD_BLANK_PRODINFO (1 << 4u)
#define EXOSPHERE_FLAGS_DEFAULT (EXOSPHERE_FLAG_IS_DEBUGMODE_PRIV) #define EXOSPHERE_FLAGS_DEFAULT (EXOSPHERE_FLAG_IS_DEBUGMODE_PRIV)
typedef struct { typedef struct {
@ -54,6 +55,7 @@ unsigned int exosphere_should_perform_620_keygen(void);
unsigned int exosphere_should_override_debugmode_priv(void); unsigned int exosphere_should_override_debugmode_priv(void);
unsigned int exosphere_should_override_debugmode_user(void); unsigned int exosphere_should_override_debugmode_user(void);
unsigned int exosphere_should_disable_usermode_exception_handlers(void); unsigned int exosphere_should_disable_usermode_exception_handlers(void);
unsigned int exosphere_should_blank_prod_info(void);
static inline unsigned int exosphere_get_target_firmware_for_init(void) { static inline unsigned int exosphere_get_target_firmware_for_init(void) {
const unsigned int magic = MAILBOX_EXOSPHERE_CONFIG_PHYS.magic; const unsigned int magic = MAILBOX_EXOSPHERE_CONFIG_PHYS.magic;

View file

@ -28,6 +28,7 @@
#define EXOSPHERE_FLAG_IS_DEBUGMODE_PRIV (1 << 1u) #define EXOSPHERE_FLAG_IS_DEBUGMODE_PRIV (1 << 1u)
#define EXOSPHERE_FLAG_IS_DEBUGMODE_USER (1 << 2u) #define EXOSPHERE_FLAG_IS_DEBUGMODE_USER (1 << 2u)
#define EXOSPHERE_FLAG_DISABLE_USERMODE_EXCEPTION_HANDLERS (1 << 3u) #define EXOSPHERE_FLAG_DISABLE_USERMODE_EXCEPTION_HANDLERS (1 << 3u)
#define EXOSPHERE_FLAG_SHOULD_BLANK_PRODINFO (1 << 4u)
#define EXOSPHERE_FLAGS_DEFAULT (EXOSPHERE_FLAG_IS_DEBUGMODE_PRIV) #define EXOSPHERE_FLAGS_DEFAULT (EXOSPHERE_FLAG_IS_DEBUGMODE_PRIV)
typedef struct { typedef struct {

View file

@ -199,6 +199,14 @@ static void nxboot_configure_exosphere(uint32_t target_firmware, unsigned int ke
fatal_error("[NXBOOT]: Failed to parse BCT.ini!\n"); fatal_error("[NXBOOT]: Failed to parse BCT.ini!\n");
} }
{
FILE *flag = fopen("atmosphere/flags/blank_prodinfo.flag", "rb");
if (flag != NULL) {
exo_cfg.flags |= EXOSPHERE_FLAG_SHOULD_BLANK_PRODINFO;
fclose(flag);
}
}
if ((exo_cfg.target_firmware < ATMOSPHERE_TARGET_FIRMWARE_MIN) || (exo_cfg.target_firmware > ATMOSPHERE_TARGET_FIRMWARE_MAX)) { if ((exo_cfg.target_firmware < ATMOSPHERE_TARGET_FIRMWARE_MIN) || (exo_cfg.target_firmware > ATMOSPHERE_TARGET_FIRMWARE_MAX)) {
fatal_error("[NXBOOT]: Invalid Exosphere target firmware!\n"); fatal_error("[NXBOOT]: Invalid Exosphere target firmware!\n");
} }

View file

@ -112,7 +112,6 @@ class IROStorage : public IStorage {
virtual Result OperateRange(u32 operation_type, u64 offset, u64 size, FsRangeInfo *out_range_info) = 0; virtual Result OperateRange(u32 operation_type, u64 offset, u64 size, FsRangeInfo *out_range_info) = 0;
}; };
class ProxyStorage : public IStorage { class ProxyStorage : public IStorage {
private: private:
FsStorage *base_storage; FsStorage *base_storage;
@ -172,4 +171,4 @@ class ROProxyStorage : public IROStorage {
virtual Result OperateRange(u32 operation_type, u64 offset, u64 size, FsRangeInfo *out_range_info) override { virtual Result OperateRange(u32 operation_type, u64 offset, u64 size, FsRangeInfo *out_range_info) override {
return fsStorageOperateRange(this->base_storage, operation_type, offset, size, out_range_info); return fsStorageOperateRange(this->base_storage, operation_type, offset, size, out_range_info);
}; };
}; };

View file

@ -0,0 +1,113 @@
/*
* Copyright (c) 2018-2019 Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <switch.h>
#include <stratosphere.hpp>
#include "fs_shim.h"
#include "../debug.hpp"
#include "fs_istorage.hpp"
class MemoryStorage : public IStorage {
private:
u8 * const buffer;
const u64 size;
public:
MemoryStorage(void *b, u64 sz) : buffer(static_cast<u8 *>(b)), size(sz) {
/* ... */
}
virtual ~MemoryStorage() {
/* ... */
}
public:
virtual Result Read(void *buffer, size_t size, u64 offset) override {
if (size == 0) {
return ResultSuccess;
}
if (buffer == nullptr) {
return ResultFsNullptrArgument;
}
if (!IStorage::IsRangeValid(offset, size, this->size)) {
return ResultFsOutOfRange;
}
std::memcpy(buffer, this->buffer + offset, size);
return ResultSuccess;
}
virtual Result Write(void *buffer, size_t size, u64 offset) override {
if (size == 0) {
return ResultSuccess;
}
if (buffer == nullptr) {
return ResultFsNullptrArgument;
}
if (!IStorage::IsRangeValid(offset, size, this->size)) {
return ResultFsOutOfRange;
}
std::memcpy(this->buffer + offset, buffer, size);
return ResultSuccess;
}
virtual Result Flush() override {
return ResultSuccess;
}
virtual Result GetSize(u64 *out_size) override {
*out_size = this->size;
return ResultSuccess;
}
virtual Result SetSize(u64 size) override {
return ResultFsUnsupportedOperation;
}
virtual Result OperateRange(u32 operation_type, u64 offset, u64 size, FsRangeInfo *out_range_info) override {
switch (operation_type) {
case 2: /* TODO: OperationType_Invalidate */
return ResultSuccess;
case 3: /* TODO: OperationType_Query */
if (out_range_info == nullptr) {
return ResultFsNullptrArgument;
}
/* N checks for size == sizeof(*out_range_info) here, but that's because their wrapper api is bad. */
std::memset(out_range_info, 0, sizeof(*out_range_info));
return ResultSuccess;
default:
return ResultFsUnsupportedOperation;
}
}
};
class ReadOnlyMemoryStorage : public MemoryStorage {
public:
ReadOnlyMemoryStorage(const void *b, u64 sz) : MemoryStorage(const_cast<void *>(b), sz) {
/* ... */
}
virtual ~ReadOnlyMemoryStorage() {
/* ... */
}
public:
virtual Result Write(void *buffer, size_t size, u64 offset) override {
return ResultFsUnsupportedOperation;
}
};

View file

@ -27,12 +27,14 @@
#include "fsmitm_boot0storage.hpp" #include "fsmitm_boot0storage.hpp"
#include "fsmitm_romstorage.hpp" #include "fsmitm_romstorage.hpp"
#include "fsmitm_layeredrom.hpp" #include "fsmitm_layeredrom.hpp"
#include "fsmitm_utils.hpp"
#include "fs_dir_utils.hpp" #include "fs_dir_utils.hpp"
#include "fs_save_utils.hpp" #include "fs_save_utils.hpp"
#include "fs_subdirectory_filesystem.hpp" #include "fs_subdirectory_filesystem.hpp"
#include "fs_directory_savedata_filesystem.hpp" #include "fs_directory_savedata_filesystem.hpp"
#include "fs_file_storage.hpp" #include "fs_file_storage.hpp"
#include "fs_memory_storage.hpp"
#include "../debug.hpp" #include "../debug.hpp"
@ -262,23 +264,13 @@ Result FsMitmService::OpenBisStorage(Out<std::shared_ptr<IStorageInterface>> out
const bool is_sysmodule = TitleIdIsSystem(this->title_id); const bool is_sysmodule = TitleIdIsSystem(this->title_id);
const bool has_bis_write_flag = Utils::HasFlag(this->title_id, "bis_write"); const bool has_bis_write_flag = Utils::HasFlag(this->title_id, "bis_write");
const bool has_cal0_read_flag = Utils::HasFlag(this->title_id, "cal_read"); const bool has_cal0_read_flag = Utils::HasFlag(this->title_id, "cal_read");
const bool has_blank_cal0_flag = Utils::HasGlobalFlag("blank_prodinfo"); const bool has_blank_cal0_flag = ShouldBlankProdInfo();
if (bis_partition_id == BisStorageId_Boot0) { if (bis_partition_id == BisStorageId_Boot0) {
storage = std::make_shared<IStorageInterface>(new Boot0Storage(bis_storage, this->title_id)); storage = std::make_shared<IStorageInterface>(new Boot0Storage(bis_storage, this->title_id));
} else if (bis_partition_id == BisStorageId_Prodinfo) { } else if (bis_partition_id == BisStorageId_Prodinfo) {
/* PRODINFO should *never* be writable. */ /* PRODINFO should *never* be writable. */
if (has_blank_cal0_flag) { if (has_blank_cal0_flag) {
FsFile file; storage = std::make_shared<IStorageInterface>(new MitmProxyStorage(std::make_unique<ReadOnlyMemoryStorage>(Utils::GetBlankProdInfoBuffer(), ProdInfoSize), bis_storage.s));
if (R_FAILED((rc = Utils::OpenBlankProdInfoFile(&file)))) {
return rc;
}
storage = std::make_shared<IStorageInterface>(new FileStorage(new ProxyFile(&file)));
if (out_storage.IsDomain()) {
out_domain_id = file.s.object_id;
}
return rc;
} else if (is_sysmodule || has_cal0_read_flag) { } else if (is_sysmodule || has_cal0_read_flag) {
storage = std::make_shared<IStorageInterface>(new ROProxyStorage(bis_storage)); storage = std::make_shared<IStorageInterface>(new ROProxyStorage(bis_storage));
} else { } else {

View file

@ -0,0 +1,59 @@
/*
* Copyright (c) 2018-2019 Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <switch.h>
#include "fs_istorage.hpp"
class MitmProxyStorage : public IStorage {
private:
Service srv_holder;
std::unique_ptr<IStorage> fwd_storage;
public:
MitmProxyStorage(std::unique_ptr<IStorage> st, Service sr = {}) : srv_holder(sr), fwd_storage(std::move(st)) {
/* ... */
}
virtual ~MitmProxyStorage() {
if (serviceIsActive(&srv_holder)) {
serviceClose(&srv_holder);
}
}
public:
virtual Result Read(void *buffer, size_t size, u64 offset) override {
return this->fwd_storage->Read(buffer, size, offset);
}
virtual Result Write(void *buffer, size_t size, u64 offset) override {
return this->fwd_storage->Write(buffer, size, offset);
}
virtual Result Flush() override {
return this->fwd_storage->Flush();
}
virtual Result GetSize(u64 *out_size) override {
return this->fwd_storage->GetSize(out_size);
}
virtual Result SetSize(u64 size) override {
return this->fwd_storage->SetSize(size);
}
virtual Result OperateRange(u32 operation_type, u64 offset, u64 size, FsRangeInfo *out_range_info) override {
return this->fwd_storage->OperateRange(operation_type, offset, size, out_range_info);
}
};

View file

@ -60,13 +60,13 @@ static HblOverrideConfig g_hbl_override_config = {
static char g_config_ini_data[0x800]; static char g_config_ini_data[0x800];
/* Backup file for CAL0 partition. */ /* Backup file for CAL0 partition. */
static constexpr size_t ProdInfoSize = 0x8000;
static constexpr const char *BlankProdInfoPath = "/atmosphere/automatic_backups/BLANK_PRODINFO.bin";
static FsFile g_cal0_file = {0}; static FsFile g_cal0_file = {0};
static u8 g_cal0_storage_backup[ProdInfoSize]; static u8 g_cal0_storage_backup[ProdInfoSize];
static u8 g_cal0_backup[ProdInfoSize]; static u8 g_cal0_backup[ProdInfoSize];
static FsFile g_blank_prodinfo_file = {0}; static HosMutex g_blank_cal0_lock;
static bool g_initialized_blank_cal0 = false;
static u8 g_blank_cal0[ProdInfoSize];
static bool IsHexadecimal(const char *str) { static bool IsHexadecimal(const char *str) {
while (*str) { while (*str) {
@ -142,9 +142,6 @@ void Utils::InitializeThreadFunc(void *args) {
fsFileFlush(&g_cal0_file); fsFileFlush(&g_cal0_file);
} }
/* Use one of the storages to make a blank prodinfo file. */
Utils::CreateBlankProdInfo();
/* NOTE: g_cal0_file is intentionally not closed here. This prevents any other process from opening it. */ /* NOTE: g_cal0_file is intentionally not closed here. This prevents any other process from opening it. */
std::memset(g_cal0_storage_backup, 0, sizeof(g_cal0_storage_backup)); std::memset(g_cal0_storage_backup, 0, sizeof(g_cal0_storage_backup));
std::memset(g_cal0_backup, 0, sizeof(g_cal0_backup)); std::memset(g_cal0_backup, 0, sizeof(g_cal0_backup));
@ -672,81 +669,60 @@ void Utils::RebootToFatalError(AtmosphereFatalErrorContext *ctx) {
BpcRebootManager::RebootForFatalError(ctx); BpcRebootManager::RebootForFatalError(ctx);
} }
void Utils::CreateBlankProdInfo() { void Utils::EnsureBlankProdInfo() {
Result rc; std::scoped_lock<HosMutex> lk(g_blank_cal0_lock);
if (g_initialized_blank_cal0) {
return;
}
u8 *cal0; /* Read CAL0 in from NAND. */
if (IsCal0Valid(g_cal0_backup)) { {
cal0 = g_cal0_backup; FsStorage cal0_storage;
} else if (IsCal0Valid(g_cal0_storage_backup)) { if (R_FAILED(fsOpenBisStorage(&cal0_storage, BisStorageId_Prodinfo)) || R_FAILED(fsStorageRead(&cal0_storage, 0, g_blank_cal0, ProdInfoSize))) {
cal0 = g_cal0_storage_backup; std::abort();
} else { }
fsStorageClose(&cal0_storage);
}
if (!IsCal0Valid(g_blank_cal0)) {
std::abort(); std::abort();
} }
const char blank_serial[] = "XAW00000000000"; const char blank_serial[] = "XAW00000000000";
std::memcpy(&cal0[0x250], blank_serial, sizeof(blank_serial) - 1); std::memcpy(&g_blank_cal0[0x250], blank_serial, sizeof(blank_serial) - 1);
static constexpr size_t NumErase = 7; static constexpr size_t NumErase = 7;
for (size_t i = 0; i < NumErase; i++) { for (size_t i = 0; i < NumErase; i++) {
static constexpr size_t s_erase_offsets[NumErase] = {0xAE0, 0x3AE0, 0x35E1, 0x36E1, 0x2B0, 0x3D70, 0x3FC0}; static constexpr size_t s_erase_offsets[NumErase] = {0xAE0, 0x3AE0, 0x35E1, 0x36E1, 0x2B0, 0x3D70, 0x3FC0};
static constexpr size_t s_erase_sizes[NumErase] = {0x800, 0x130, 0x6, 0x6, 0x180, 0x240, 0x240}; static constexpr size_t s_erase_sizes[NumErase] = {0x800, 0x130, 0x6, 0x6, 0x180, 0x240, 0x240};
std::memset(&cal0[s_erase_offsets[i]], 0, s_erase_sizes[i]); std::memset(&g_blank_cal0[s_erase_offsets[i]], 0, s_erase_sizes[i]);
} }
static constexpr size_t NumHashes = 2; static constexpr size_t NumHashes = 2;
{ {
static constexpr size_t s_hash_offsets[NumHashes] = {0x12E0, 0x20}; static constexpr size_t s_hash_offsets[NumHashes] = {0x12E0, 0x20};
static constexpr size_t s_data_offsets[NumHashes] = {0xAE0, 0x40}; static constexpr size_t s_data_offsets[NumHashes] = {0xAE0, 0x40};
const size_t data_sizes[NumHashes] = {*reinterpret_cast<u32 *>(&cal0[0xAD0]), *reinterpret_cast<u32 *>(&cal0[0x8])}; const size_t data_sizes[NumHashes] = {*reinterpret_cast<u32 *>(&g_blank_cal0[0xAD0]), *reinterpret_cast<u32 *>(&g_blank_cal0[0x8])};
for (size_t i = 0; i < NumHashes; i++) { for (size_t i = 0; i < NumHashes; i++) {
u8 hash[SHA256_HASH_SIZE]; u8 hash[SHA256_HASH_SIZE];
sha256CalculateHash(hash, &cal0[s_data_offsets[i]], data_sizes[i]); sha256CalculateHash(hash, &g_blank_cal0[s_data_offsets[i]], data_sizes[i]);
std::memcpy(&cal0[s_hash_offsets[i]], hash, sizeof(hash)); std::memcpy(&g_blank_cal0[s_hash_offsets[i]], hash, sizeof(hash));
} }
} }
/* File creation is allowed to fail, because it may already exist. */ g_initialized_blank_cal0 = true;
fsFsCreateFile(&g_sd_filesystem, BlankProdInfoPath, ProdInfoSize, 0);
FsFile f;
if (R_FAILED((rc = fsFsOpenFile(&g_sd_filesystem, BlankProdInfoPath, FS_OPEN_READ | FS_OPEN_WRITE, &f)))) {
std::abort();
}
if (R_FAILED((rc = fsFileSetSize(&f, ProdInfoSize)))) {
std::abort();
}
if (R_FAILED((rc = fsFileWrite(&f, 0, cal0, ProdInfoSize)))) {
std::abort();
}
if (R_FAILED((rc = fsFileFlush(&f)))) {
std::abort();
}
fsFileClose(&f);
if (R_FAILED((rc = fsFsOpenFile(&g_sd_filesystem, BlankProdInfoPath, FS_OPEN_READ, &g_blank_prodinfo_file)))) {
std::abort();
}
} }
bool Utils::IsCal0Valid(const u8 *cal0) { bool Utils::IsCal0Valid(const u8 *cal0) {
char serial_number[0x40] = {0};
std::memcpy(serial_number, cal0 + 0x250, 0x18);
bool is_cal0_valid = true; bool is_cal0_valid = true;
is_cal0_valid &= std::memcmp(g_cal0_backup, "CAL0", 4) == 0; is_cal0_valid &= std::memcmp(cal0, "CAL0", 4) == 0;
is_cal0_valid &= std::memcmp(g_cal0_backup + 0x250, serial_number, 0x18) == 0; u32 cal0_size = ((u32 *)cal0)[2];
u32 cal0_size = ((u32 *)g_cal0_backup)[2];
is_cal0_valid &= cal0_size + 0x40 <= ProdInfoSize; is_cal0_valid &= cal0_size + 0x40 <= ProdInfoSize;
if (is_cal0_valid) { if (is_cal0_valid) {
u8 calc_hash[0x20]; u8 calc_hash[0x20];
sha256CalculateHash(calc_hash, g_cal0_backup + 0x40, cal0_size); sha256CalculateHash(calc_hash, cal0 + 0x40, cal0_size);
is_cal0_valid &= std::memcmp(calc_hash, g_cal0_backup + 0x20, sizeof(calc_hash)) == 0; is_cal0_valid &= std::memcmp(calc_hash, cal0 + 0x20, sizeof(calc_hash)) == 0;
} }
return is_cal0_valid; return is_cal0_valid;
@ -771,15 +747,7 @@ u16 Utils::GetCrc16(const void *data, size_t size) {
return crc16; return crc16;
} }
Result Utils::OpenBlankProdInfoFile(FsFile *out) { const void *Utils::GetBlankProdInfoBuffer() {
if (!IsSdInitialized()) { EnsureBlankProdInfo();
std::abort(); return g_blank_cal0;
return ResultFsSdCardNotPresent; }
}
Result rc = OpenSdFile(BlankProdInfoPath, FS_OPEN_READ, out);
if (R_FAILED((rc))) {
std::abort();
}
return rc;
}

View file

@ -37,6 +37,8 @@ enum BisStorageId : u32 {
BisStorageId_SystemProperPartition = 33, BisStorageId_SystemProperPartition = 33,
}; };
static constexpr size_t ProdInfoSize = 0x8000;
struct OverrideKey { struct OverrideKey {
u64 key_combination; u64 key_combination;
bool override_by_default; bool override_by_default;
@ -61,7 +63,7 @@ class Utils {
static bool HasSdRomfsContent(u64 title_id); static bool HasSdRomfsContent(u64 title_id);
static Result OpenBlankProdInfoFile(FsFile *out); static const void *GetBlankProdInfoBuffer();
/* Delayed Initialization + MitM detection. */ /* Delayed Initialization + MitM detection. */
static void InitializeThreadFunc(void *args); static void InitializeThreadFunc(void *args);
@ -98,6 +100,6 @@ class Utils {
private: private:
static void RefreshConfiguration(); static void RefreshConfiguration();
static void CreateBlankProdInfo(); static void EnsureBlankProdInfo();
static bool IsCal0Valid(const u8 *cal0); static bool IsCal0Valid(const u8 *cal0);
}; };

@ -1 +1 @@
Subproject commit 8ec43f0d69463784c00ddb19c5f3b043e713548e Subproject commit 5fe1dacee28265af5ed8272a3c9921904d6f50fe