Merge fs.mitm and set.mitm.

This commit is contained in:
Michael Scire
2019-01-29 02:29:29 -08:00
parent 5f836aca6d
commit e715197290
50 changed files with 426 additions and 630 deletions

View File

@@ -0,0 +1,97 @@
/*
* Copyright (c) 2018 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/>.
*/
#include <cstdlib>
#include <cstdint>
#include <cstring>
#include <malloc.h>
#include <switch.h>
#include <atmosphere.h>
#include <stratosphere.hpp>
#include "amsmitm_modules.hpp"
#include "utils.hpp"
extern "C" {
extern u32 __start__;
u32 __nx_applet_type = AppletType_None;
#define INNER_HEAP_SIZE 0x1000000
size_t nx_inner_heap_size = INNER_HEAP_SIZE;
char nx_inner_heap[INNER_HEAP_SIZE];
void __libnx_initheap(void);
void __appInit(void);
void __appExit(void);
}
void __libnx_initheap(void) {
void* addr = nx_inner_heap;
size_t size = nx_inner_heap_size;
/* Newlib */
extern char* fake_heap_start;
extern char* fake_heap_end;
fake_heap_start = (char*)addr;
fake_heap_end = (char*)addr + size;
}
void __appInit(void) {
Result rc;
rc = smInitialize();
if (R_FAILED(rc)) {
fatalSimple(MAKERESULT(Module_Libnx, LibnxError_InitFail_SM));
}
rc = fsInitialize();
if (R_FAILED(rc)) {
fatalSimple(MAKERESULT(Module_Libnx, LibnxError_InitFail_FS));
}
CheckAtmosphereVersion(CURRENT_ATMOSPHERE_VERSION);
}
void __appExit(void) {
/* Cleanup services. */
fsExit();
smExit();
}
int main(int argc, char **argv)
{
consoleDebugInit(debugDevice_SVC);
HosThread initializer_thread;
LaunchAllMitmModules();
if (R_FAILED(initializer_thread.Initialize(&Utils::InitializeThreadFunc, NULL, 0x4000, 0x15))) {
/* TODO: Panic. */
}
if (R_FAILED(initializer_thread.Start())) {
/* TODO: Panic. */
}
/* Wait for all mitm modules to end. */
WaitAllMitmModules();
return 0;
}

View File

@@ -0,0 +1,60 @@
/*
* Copyright (c) 2018 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/>.
*/
#include <switch.h>
#include <stratosphere.hpp>
#include <cstring>
#include "debug.hpp"
#include "amsmitm_modules.hpp"
#include "fs_mitm/fsmitm_main.hpp"
#include "set_mitm/setmitm_main.hpp"
static HosThread g_module_threads[MitmModuleId_Count];
static const struct {
ThreadFunc main;
u32 priority;
u32 stack_size;
} g_module_definitions[MitmModuleId_Count] = {
{ &FsMitmMain, FsMitmPriority, FsMitmStackSize }, /* FsMitm */
{ &SetMitmMain, SetMitmPriority, SetMitmStackSize }, /* SetMitm */
};
void LaunchAllMitmModules() {
/* Create thread for each module. */
for (u32 i = 0; i < static_cast<u32>(MitmModuleId_Count); i++) {
const auto cur_module = &g_module_definitions[i];
if (R_FAILED(g_module_threads[i].Initialize(cur_module->main, nullptr, cur_module->stack_size, cur_module->priority))) {
std::abort();
}
}
/* Start thread for each module. */
for (u32 i = 0; i < static_cast<u32>(MitmModuleId_Count); i++) {
if (R_FAILED(g_module_threads[i].Start())) {
std::abort();
}
}
}
void WaitAllMitmModules() {
/* Wait on thread for each module. */
for (u32 i = 0; i < static_cast<u32>(MitmModuleId_Count); i++) {
g_module_threads[i].Join();
}
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright (c) 2018 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
enum MitmModuleId : u32 {
MitmModuleId_FsMitm = 0,
MitmModuleId_SetMitm = 1,
MitmModuleId_Count = 2,
};
void LaunchAllMitmModules();
void WaitAllMitmModules();

View File

@@ -0,0 +1,28 @@
/*
* Copyright (c) 2018 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/>.
*/
#include <switch.h>
#include <stratosphere.hpp>
#include <cstring>
#include "debug.hpp"
void Reboot() {
/* ... */
}
void Log(const void *data, int size) {
/* ... */
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright (c) 2018 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
void Reboot();
void Log(const void *data, int size);

View File

@@ -0,0 +1,171 @@
/*
* Copyright (c) 2018 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"
enum FsIStorageCmd : u32 {
FsIStorageCmd_Read = 0,
FsIStorageCmd_Write = 1,
FsIStorageCmd_Flush = 2,
FsIStorageCmd_SetSize = 3,
FsIStorageCmd_GetSize = 4,
FsIStorageCmd_OperateRange = 5,
};
class IStorage {
public:
virtual ~IStorage();
virtual Result Read(void *buffer, size_t size, u64 offset) = 0;
virtual Result Write(void *buffer, size_t size, u64 offset) = 0;
virtual Result Flush() = 0;
virtual Result SetSize(u64 size) = 0;
virtual Result GetSize(u64 *out_size) = 0;
virtual Result OperateRange(u32 operation_type, u64 offset, u64 size, FsRangeInfo *out_range_info) = 0;
};
class IStorageInterface : public IServiceObject {
private:
IStorage *base_storage;
public:
IStorageInterface(IStorage *s) : base_storage(s) {
/* ... */
};
~IStorageInterface() {
delete base_storage;
};
private:
/* Actual command API. */
virtual Result Read(OutBuffer<u8, BufferType_Type1> buffer, u64 offset, u64 size) final {
return this->base_storage->Read(buffer.buffer, std::min(buffer.num_elements, size), offset);
};
virtual Result Write(InBuffer<u8, BufferType_Type1> buffer, u64 offset, u64 size) final {
return this->base_storage->Write(buffer.buffer, std::min(buffer.num_elements, size), offset);
};
virtual Result Flush() final {
return this->base_storage->Flush();
};
virtual Result SetSize(u64 size) final {
return this->base_storage->SetSize(size);
};
virtual Result GetSize(Out<u64> size) final {
return this->base_storage->GetSize(size.GetPointer());
};
virtual Result OperateRange(Out<FsRangeInfo> range_info, u32 operation_type, u64 offset, u64 size) final {
return this->base_storage->OperateRange(operation_type, offset, size, range_info.GetPointer());
};
public:
DEFINE_SERVICE_DISPATCH_TABLE {
/* 1.0.0- */
MakeServiceCommandMeta<FsIStorageCmd_Read, &IStorageInterface::Read>(),
MakeServiceCommandMeta<FsIStorageCmd_Write, &IStorageInterface::Write>(),
MakeServiceCommandMeta<FsIStorageCmd_Flush, &IStorageInterface::Flush>(),
MakeServiceCommandMeta<FsIStorageCmd_SetSize, &IStorageInterface::SetSize>(),
MakeServiceCommandMeta<FsIStorageCmd_GetSize, &IStorageInterface::GetSize>(),
/* 4.0.0- */
MakeServiceCommandMeta<FsIStorageCmd_OperateRange, &IStorageInterface::OperateRange, FirmwareVersion_400>(),
};
};
class IROStorage : public IStorage {
public:
virtual Result Read(void *buffer, size_t size, u64 offset) = 0;
virtual Result Write(void *buffer, size_t size, u64 offset) final {
(void)(buffer);
(void)(offset);
(void)(size);
return 0x313802;
};
virtual Result Flush() final {
return 0x0;
};
virtual Result SetSize(u64 size) final {
(void)(size);
return 0x313802;
};
virtual Result GetSize(u64 *out_size) = 0;
virtual Result OperateRange(u32 operation_type, u64 offset, u64 size, FsRangeInfo *out_range_info) = 0;
};
class ProxyStorage : public IStorage {
private:
FsStorage *base_storage;
public:
ProxyStorage(FsStorage *s) : base_storage(s) {
/* ... */
};
ProxyStorage(FsStorage s) {
this->base_storage = new FsStorage(s);
};
virtual ~ProxyStorage() {
fsStorageClose(base_storage);
delete base_storage;
};
public:
virtual Result Read(void *buffer, size_t size, u64 offset) override {
return fsStorageRead(this->base_storage, offset, buffer, size);
};
virtual Result Write(void *buffer, size_t size, u64 offset) override {
return fsStorageWrite(this->base_storage, offset, buffer, size);
};
virtual Result Flush() override {
return fsStorageFlush(this->base_storage);
};
virtual Result GetSize(u64 *out_size) override {
return fsStorageGetSize(this->base_storage, out_size);
};
virtual Result SetSize(u64 size) override {
return fsStorageSetSize(this->base_storage, size);
};
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);
};
};
class ROProxyStorage : public IROStorage {
private:
FsStorage *base_storage;
public:
ROProxyStorage(FsStorage *s) : base_storage(s) {
/* ... */
};
ROProxyStorage(FsStorage s) {
this->base_storage = new FsStorage(s);
};
virtual ~ROProxyStorage() {
fsStorageClose(base_storage);
delete base_storage;
};
public:
virtual Result Read(void *buffer, size_t size, u64 offset) override {
return fsStorageRead(this->base_storage, offset, buffer, size);
};
virtual Result GetSize(u64 *out_size) override {
return fsStorageGetSize(this->base_storage, out_size);
};
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);
};
};

View File

@@ -0,0 +1,215 @@
/*
* Copyright (c) 2018 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/>.
*/
#include <switch.h>
#include "fs_shim.h"
/* Missing fsp-srv commands. */
Result fsOpenBisStorageFwd(Service* s, FsStorage* out, u32 PartitionId) {
IpcCommand c;
ipcInitialize(&c);
struct {
u64 magic;
u64 cmd_id;
u32 PartitionId;
} *raw;
raw = serviceIpcPrepareHeader(s, &c, sizeof(*raw));
raw->magic = SFCI_MAGIC;
raw->cmd_id = 12;
raw->PartitionId = PartitionId;
Result rc = serviceIpcDispatch(s);
if (R_SUCCEEDED(rc)) {
IpcParsedCommand r;
struct {
u64 magic;
u64 result;
} *resp;
serviceIpcParse(s, &r, sizeof(*resp));
resp = r.Raw;
rc = resp->result;
if (R_SUCCEEDED(rc)) {
serviceCreateSubservice(&out->s, s, &r, 0);
}
}
return rc;
}
Result fsOpenDataStorageByCurrentProcessFwd(Service* s, FsStorage* out) {
IpcCommand c;
ipcInitialize(&c);
struct {
u64 magic;
u64 cmd_id;
} *raw;
raw = serviceIpcPrepareHeader(s, &c, sizeof(*raw));
raw->magic = SFCI_MAGIC;
raw->cmd_id = 200;
Result rc = serviceIpcDispatch(s);
if (R_SUCCEEDED(rc)) {
IpcParsedCommand r;
struct {
u64 magic;
u64 result;
} *resp;
serviceIpcParse(s, &r, sizeof(*resp));
resp = r.Raw;
rc = resp->result;
if (R_SUCCEEDED(rc)) {
serviceCreateSubservice(&out->s, s, &r, 0);
}
}
return rc;
}
Result fsOpenDataStorageByDataIdFwd(Service* s, FsStorageId storage_id, u64 data_id, FsStorage* out) {
IpcCommand c;
ipcInitialize(&c);
struct {
u64 magic;
u64 cmd_id;
FsStorageId storage_id;
u64 data_id;
} *raw;
raw = serviceIpcPrepareHeader(s, &c, sizeof(*raw));
raw->magic = SFCI_MAGIC;
raw->cmd_id = 202;
raw->storage_id = storage_id;
raw->data_id = data_id;
Result rc = serviceIpcDispatch(s);
if (R_SUCCEEDED(rc)) {
IpcParsedCommand r;
struct {
u64 magic;
u64 result;
} *resp;
serviceIpcParse(s, &r, sizeof(*resp));
resp = r.Raw;
rc = resp->result;
if (R_SUCCEEDED(rc)) {
serviceCreateSubservice(&out->s, s, &r, 0);
}
}
return rc;
}
/* Missing FS File commands. */
Result fsFileOperateRange(FsFile* f, u32 op_id, u64 off, u64 len, FsRangeInfo *out) {
IpcCommand c;
ipcInitialize(&c);
struct {
u64 magic;
u64 cmd_id;
u32 op_id;
u64 off;
u64 len;
} *raw;
raw = serviceIpcPrepareHeader(&f->s, &c, sizeof(*raw));
raw->magic = SFCI_MAGIC;
raw->cmd_id = 5;
raw->op_id = op_id;
raw->off = off;
raw->len = len;
Result rc = serviceIpcDispatch(&f->s);
if (R_SUCCEEDED(rc)) {
IpcParsedCommand r;
struct {
u64 magic;
u64 result;
FsRangeInfo range_info;
} *resp;
serviceIpcParse(&f->s, &r, sizeof(*resp));
resp = r.Raw;
rc = resp->result;
if (R_SUCCEEDED(rc) && out) *out = resp->range_info;
}
return rc;
}
/* Missing FS Storage commands. */
Result fsStorageOperateRange(FsStorage* s, u32 op_id, u64 off, u64 len, FsRangeInfo *out) {
IpcCommand c;
ipcInitialize(&c);
struct {
u64 magic;
u64 cmd_id;
u32 op_id;
u64 off;
u64 len;
} *raw;
raw = serviceIpcPrepareHeader(&s->s, &c, sizeof(*raw));
raw->magic = SFCI_MAGIC;
raw->cmd_id = 5;
raw->op_id = op_id;
raw->off = off;
raw->len = len;
Result rc = serviceIpcDispatch(&s->s);
if (R_SUCCEEDED(rc)) {
IpcParsedCommand r;
struct {
u64 magic;
u64 result;
FsRangeInfo range_info;
} *resp;
serviceIpcParse(&s->s, &r, sizeof(*resp));
resp = r.Raw;
rc = resp->result;
if (R_SUCCEEDED(rc) && out) *out = resp->range_info;
}
return rc;
}

View File

@@ -0,0 +1,32 @@
/**
* @file fs_shim.h
* @brief Filesystem Services (fs) IPC wrapper. To be merged into libnx, eventually.
* @author SciresM
* @copyright libnx Authors
*/
#pragma once
#include <switch.h>
#ifdef __cplusplus
extern "C" {
#endif
/* TODO: Reverse this more. */
typedef struct {
u32 flags[0x40/sizeof(u32)];
} FsRangeInfo;
/* Missing fsp-srv commands. */
Result fsOpenBisStorageFwd(Service* s, FsStorage* out, u32 PartitionId);
Result fsOpenDataStorageByCurrentProcessFwd(Service* s, FsStorage* out);
Result fsOpenDataStorageByDataIdFwd(Service* s, FsStorageId storage_id, u64 data_id, FsStorage* out);
/* Missing FS File commands. */
Result fsFileOperateRange(FsFile* f, u32 op_id, u64 off, u64 len, FsRangeInfo *out);
/* Missing FS Storage commands. */
Result fsStorageOperateRange(FsStorage* s, u32 op_id, u64 off, u64 len, FsRangeInfo *out);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,108 @@
/*
* Copyright (c) 2018 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/>.
*/
#include <switch.h>
#include <cstring>
#include <stratosphere.hpp>
#include "fsmitm_boot0storage.hpp"
static HosMutex g_boot0_mutex;
static u8 g_boot0_bct_buffer[Boot0Storage::BctEndOffset];
bool Boot0Storage::CanModifyBctPubks() {
return this->title_id != 0x010000000000001FULL;
}
Result Boot0Storage::Read(void *_buffer, size_t size, u64 offset) {
std::scoped_lock<HosMutex> lk{g_boot0_mutex};
return Base::Read(_buffer, size, offset);
}
Result Boot0Storage::Write(void *_buffer, size_t size, u64 offset) {
std::scoped_lock<HosMutex> lk{g_boot0_mutex};
Result rc = 0;
u8 *buffer = static_cast<u8 *>(_buffer);
/* Protect the keyblob region from writes. */
if (offset <= EksStart) {
if (offset + size < EksStart) {
/* Fall through, no need to do anything here. */
} else {
if (offset + size < EksEnd) {
/* Adjust size to avoid writing end of data. */
size = EksStart - offset;
} else {
/* Perform portion of write falling past end of keyblobs. */
const u64 diff = EksEnd - offset;
if (R_FAILED((rc = Base::Write(buffer + diff, size - diff, EksEnd)))) {
return rc;
}
/* Adjust size to avoid writing end of data. */
size = EksStart - offset;
}
}
} else {
if (offset < EksEnd) {
if (offset + size < EksEnd) {
/* Ignore writes falling strictly within the region. */
return 0;
} else {
/* Only write past the end of the keyblob region. */
buffer = buffer + (EksEnd - offset);
size -= (EksEnd - offset);
offset = EksEnd;
}
} else {
/* Fall through, no need to do anything here. */
}
}
if (size == 0) {
return 0;
}
/* We care about protecting autorcm from NS. */
if (CanModifyBctPubks() || offset >= BctEndOffset || (offset + BctSize >= BctEndOffset && offset % BctSize >= BctPubkEnd)) {
return Base::Write(buffer, size, offset);
}
/* First, let's deal with the data past the end. */
if (offset + size >= BctEndOffset) {
const u64 diff = BctEndOffset - offset;
if (R_FAILED((rc = ProxyStorage::Write(buffer + diff, size - diff, BctEndOffset)))) {
return rc;
}
size = diff;
}
/* Read in the current BCT region. */
if (R_FAILED((rc = ProxyStorage::Read(g_boot0_bct_buffer, BctEndOffset, 0)))) {
return rc;
}
/* Update the bct buffer. */
for (u64 cur_ofs = offset; cur_ofs < BctEndOffset && cur_ofs < offset + size; cur_ofs++) {
const u64 cur_bct_rel_ofs = cur_ofs % BctSize;
if (cur_bct_rel_ofs < BctPubkStart || BctPubkEnd <= cur_bct_rel_ofs) {
g_boot0_bct_buffer[cur_ofs] = buffer[cur_ofs - offset];
}
}
return ProxyStorage::Write(g_boot0_bct_buffer, BctEndOffset, 0);
}

View File

@@ -0,0 +1,159 @@
/*
* Copyright (c) 2018 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 <cstring>
#include <stratosphere.hpp>
#include "fs_istorage.hpp"
/* Represents a sectored storage. */
template<u64 SectorSize>
class SectoredProxyStorage : public ProxyStorage {
private:
u64 cur_seek = 0;
u64 cur_sector = 0;
u64 cur_sector_ofs = 0;
u8 sector_buf[SectorSize];
private:
void Seek(u64 offset) {
this->cur_sector_ofs = offset % SectorSize;
this->cur_seek = offset - this->cur_sector_ofs;
}
public:
SectoredProxyStorage(FsStorage *s) : ProxyStorage(s) { }
SectoredProxyStorage(FsStorage s) : ProxyStorage(s) { }
public:
virtual Result Read(void *_buffer, size_t size, u64 offset) override {
Result rc = 0;
u8 *buffer = static_cast<u8 *>(_buffer);
this->Seek(offset);
if (this->cur_sector_ofs == 0 && size % SectorSize == 0) {
/* Fast case. */
return ProxyStorage::Read(buffer, size, offset);
}
if (R_FAILED((rc = ProxyStorage::Read(this->sector_buf, SectorSize, this->cur_seek)))) {
return rc;
}
if (size + this->cur_sector_ofs <= SectorSize) {
memcpy(buffer, sector_buf + this->cur_sector_ofs, size);
} else {
/* Leaving the sector... */
size_t ofs = SectorSize - this->cur_sector_ofs;
memcpy(buffer, sector_buf + this->cur_sector_ofs, ofs);
size -= ofs;
/* We're guaranteed alignment, here. */
const size_t aligned_remaining_size = size - (size % SectorSize);
if (aligned_remaining_size) {
if (R_FAILED((rc = ProxyStorage::Read(buffer + ofs, aligned_remaining_size, offset + ofs)))) {
return rc;
}
ofs += aligned_remaining_size;
size -= aligned_remaining_size;
}
/* Read any leftover data. */
if (size) {
if (R_FAILED((rc = ProxyStorage::Read(this->sector_buf, SectorSize, offset + ofs)))) {
return rc;
}
memcpy(buffer + ofs, sector_buf, size);
}
}
return rc;
};
virtual Result Write(void *_buffer, size_t size, u64 offset) override {
Result rc = 0;
u8 *buffer = static_cast<u8 *>(_buffer);
this->Seek(offset);
if (this->cur_sector_ofs == 0 && size % SectorSize == 0) {
/* Fast case. */
return ProxyStorage::Write(buffer, size, offset);
}
if (R_FAILED((rc = ProxyStorage::Read(this->sector_buf, SectorSize, this->cur_seek)))) {
return rc;
}
if (size + this->cur_sector_ofs <= SectorSize) {
memcpy(this->sector_buf + this->cur_sector_ofs, buffer, size);
rc = ProxyStorage::Write(this->sector_buf, SectorSize, this->cur_seek);
} else {
/* Leaving the sector... */
size_t ofs = SectorSize - this->cur_sector_ofs;
memcpy(this->sector_buf + this->cur_sector_ofs, buffer, ofs);
if (R_FAILED((rc = ProxyStorage::Write(this->sector_buf, ofs, this->cur_seek)))) {
return rc;
}
size -= ofs;
/* We're guaranteed alignment, here. */
const size_t aligned_remaining_size = size - (size % SectorSize);
if (aligned_remaining_size) {
if (R_FAILED((rc = ProxyStorage::Write(buffer + ofs, aligned_remaining_size, offset + ofs)))) {
return rc;
}
ofs += aligned_remaining_size;
size -= aligned_remaining_size;
}
/* Write any leftover data. */
if (size) {
if (R_FAILED((rc = ProxyStorage::Read(this->sector_buf, SectorSize, offset + ofs)))) {
return rc;
}
memcpy(this->sector_buf, buffer + ofs, size);
rc = ProxyStorage::Write(this->sector_buf, SectorSize, this->cur_seek);
}
}
return rc;
};
};
/* Represents an RCM-preserving BOOT0 partition. */
class Boot0Storage : public SectoredProxyStorage<0x200> {
using Base = SectoredProxyStorage<0x200>;
public:
static constexpr u64 BctEndOffset = 0xFC000;
static constexpr u64 BctSize = 0x4000;
static constexpr u64 BctPubkStart = 0x210;
static constexpr u64 BctPubkSize = 0x100;
static constexpr u64 BctPubkEnd = BctPubkStart + BctPubkSize;
static constexpr u64 EksStart = 0x180000;
static constexpr u64 EksSize = 0x4000;
static constexpr u64 EksEnd = EksStart + EksSize;
private:
u64 title_id;
private:
bool CanModifyBctPubks();
public:
Boot0Storage(FsStorage *s, u64 t) : Base(s), title_id(t) { }
Boot0Storage(FsStorage s, u64 t) : Base(s), title_id(t) { }
public:
virtual Result Read(void *_buffer, size_t size, u64 offset) override;
virtual Result Write(void *_buffer, size_t size, u64 offset) override;
};

View File

@@ -0,0 +1,171 @@
/*
* Copyright (c) 2018 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/>.
*/
#include <switch.h>
#include <stratosphere.hpp>
#include "fsmitm_layeredrom.hpp"
#include "../utils.hpp"
#include "../debug.hpp"
IStorage::~IStorage() = default;
LayeredRomFS::LayeredRomFS(std::shared_ptr<RomInterfaceStorage> s_r, std::shared_ptr<RomFileStorage> f_r, u64 tid) : storage_romfs(s_r), file_romfs(f_r), title_id(tid) {
/* Start building the new virtual romfs. */
RomFSBuildContext build_ctx(this->title_id);
this->p_source_infos = std::shared_ptr<std::vector<RomFSSourceInfo>>(new std::vector<RomFSSourceInfo>(), [](std::vector<RomFSSourceInfo> *to_delete) {
for (unsigned int i = 0; i < to_delete->size(); i++) {
(*to_delete)[i].Cleanup();
}
delete to_delete;
});
if (Utils::IsSdInitialized()) {
build_ctx.MergeSdFiles();
}
if (this->file_romfs) {
build_ctx.MergeRomStorage(this->file_romfs.get(), RomFSDataSource::FileRomFS);
}
if (this->storage_romfs) {
build_ctx.MergeRomStorage(this->storage_romfs.get(), RomFSDataSource::BaseRomFS);
}
build_ctx.Build(this->p_source_infos.get());
}
Result LayeredRomFS::Read(void *buffer, size_t size, u64 offset) {
/* Size zero reads should always succeed. */
if (size == 0) {
return 0;
}
/* Validate size. */
u64 virt_size = (*this->p_source_infos)[this->p_source_infos->size() - 1].virtual_offset + (*this->p_source_infos)[this->p_source_infos->size() - 1].size;
if (offset >= virt_size) {
return 0x2F5A02;
}
if (virt_size - offset < size) {
size = virt_size - offset;
}
/* Find first source info via binary search. */
u32 cur_source_ind = 0;
u32 low = 0, high = this->p_source_infos->size() - 1;
while (low <= high) {
u32 mid = (low + high) / 2;
if ((*this->p_source_infos)[mid].virtual_offset > offset) {
/* Too high. */
high = mid - 1;
} else {
/* sources[mid].virtual_offset <= offset, invariant */
if (mid == this->p_source_infos->size() - 1 || (*this->p_source_infos)[mid + 1].virtual_offset > offset) {
/* Success */
cur_source_ind = mid;
break;
}
low = mid + 1;
}
}
Result rc;
size_t read_so_far = 0;
while (read_so_far < size) {
RomFSSourceInfo *cur_source = &((*this->p_source_infos)[cur_source_ind]);
if (cur_source->virtual_offset + cur_source->size > offset) {
u64 cur_read_size = size - read_so_far;
if (cur_read_size > cur_source->size - (offset - cur_source->virtual_offset)) {
cur_read_size = cur_source->size - (offset - cur_source->virtual_offset);
}
switch (cur_source->type) {
case RomFSDataSource::MetaData:
{
FsFile file;
if (R_FAILED((rc = Utils::OpenSdFileForAtmosphere(this->title_id, ROMFS_METADATA_FILE_PATH, FS_OPEN_READ, &file)))) {
fatalSimple(rc);
}
size_t out_read;
if (R_FAILED((rc = fsFileRead(&file, (offset - cur_source->virtual_offset), (void *)((uintptr_t)buffer + read_so_far), cur_read_size, &out_read)))) {
fatalSimple(rc);
}
if (out_read != cur_read_size) {
Reboot();
}
fsFileClose(&file);
}
break;
case RomFSDataSource::LooseFile:
{
FsFile file;
if (R_FAILED((rc = Utils::OpenRomFSSdFile(this->title_id, cur_source->loose_source_info.path, FS_OPEN_READ, &file)))) {
fatalSimple(rc);
}
size_t out_read;
if (R_FAILED((rc = fsFileRead(&file, (offset - cur_source->virtual_offset), (void *)((uintptr_t)buffer + read_so_far), cur_read_size, &out_read)))) {
fatalSimple(rc);
}
if (out_read != cur_read_size) {
Reboot();
}
fsFileClose(&file);
}
break;
case RomFSDataSource::Memory:
{
memcpy((void *)((uintptr_t)buffer + read_so_far), cur_source->memory_source_info.data + (offset - cur_source->virtual_offset), cur_read_size);
}
break;
case RomFSDataSource::BaseRomFS:
{
if (R_FAILED((rc = this->storage_romfs->Read((void *)((uintptr_t)buffer + read_so_far), cur_read_size, cur_source->base_source_info.offset + (offset - cur_source->virtual_offset))))) {
/* TODO: Can this ever happen? */
/* fatalSimple(rc); */
return rc;
}
}
break;
case RomFSDataSource::FileRomFS:
{
if (R_FAILED((rc = this->file_romfs->Read((void *)((uintptr_t)buffer + read_so_far), cur_read_size, cur_source->base_source_info.offset + (offset - cur_source->virtual_offset))))) {
fatalSimple(rc);
}
}
break;
default:
fatalSimple(0xF601);
}
read_so_far += cur_read_size;
offset += cur_read_size;
} else {
/* Handle padding explicitly. */
cur_source_ind++;
/* Zero out the padding we skip, here. */
memset((void *)((uintptr_t)buffer + read_so_far), 0, ((*this->p_source_infos)[cur_source_ind]).virtual_offset - offset);
read_so_far += ((*this->p_source_infos)[cur_source_ind]).virtual_offset - offset;
offset = ((*this->p_source_infos)[cur_source_ind]).virtual_offset;
}
}
return 0;
}
Result LayeredRomFS::GetSize(u64 *out_size) {
*out_size = (*this->p_source_infos)[this->p_source_infos->size() - 1].virtual_offset + (*this->p_source_infos)[this->p_source_infos->size() - 1].size;
return 0x0;
}
Result LayeredRomFS::OperateRange(u32 operation_type, u64 offset, u64 size, FsRangeInfo *out_range_info) {
/* TODO: How should I implement this for a virtual romfs? */
if (operation_type == 3) {
*out_range_info = {0};
}
return 0;
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright (c) 2018 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 "fsmitm_romstorage.hpp"
#include "fsmitm_romfsbuild.hpp"
#include "../utils.hpp"
/* Represents a merged RomFS. */
class LayeredRomFS : public IROStorage {
private:
/* Data Sources. */
std::shared_ptr<RomInterfaceStorage> storage_romfs;
std::shared_ptr<RomFileStorage> file_romfs;
/* Information about the merged RomFS. */
u64 title_id;
std::shared_ptr<std::vector<RomFSSourceInfo>> p_source_infos;
public:
LayeredRomFS(std::shared_ptr<RomInterfaceStorage> s_r, std::shared_ptr<RomFileStorage> f_r, u64 tid);
virtual ~LayeredRomFS() = default;
virtual Result Read(void *buffer, size_t size, u64 offset) override;
virtual Result GetSize(u64 *out_size) override;
virtual Result OperateRange(u32 operation_type, u64 offset, u64 size, FsRangeInfo *out_range_info) override;
};

View File

@@ -0,0 +1,50 @@
/*
* Copyright (c) 2018 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/>.
*/
#include <cstdlib>
#include <cstdint>
#include <cstring>
#include <malloc.h>
#include <switch.h>
#include <atmosphere.h>
#include <stratosphere.hpp>
#include "fsmitm_main.hpp"
#include "fsmitm_service.hpp"
#include "../utils.hpp"
struct FsMitmManagerOptions {
static const size_t PointerBufferSize = 0x800;
static const size_t MaxDomains = 0x40;
static const size_t MaxDomainObjects = 0x4000;
};
using FsMitmManager = WaitableManager<FsMitmManagerOptions>;
void FsMitmMain(void *arg) {
/* Create server manager. */
auto server_manager = new FsMitmManager(5);
/* Create fsp-srv mitm. */
AddMitmServerToManager<FsMitmService>(server_manager, "fsp-srv", 61);
/* Loop forever, servicing our services. */
server_manager->Process();
delete server_manager;
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright (c) 2018 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/>.
*/
#include <cstdlib>
#include <cstdint>
#include <cstring>
#include <malloc.h>
#include <switch.h>
#include <atmosphere.h>
#include <stratosphere.hpp>
constexpr u32 FsMitmPriority = 43;
constexpr u32 FsMitmStackSize = 0x8000;
void FsMitmMain(void *arg);

View File

@@ -0,0 +1,411 @@
/*
* Copyright (c) 2018 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/>.
*/
#include <switch.h>
#include <string.h>
#include <stratosphere.hpp>
#include "../utils.hpp"
#include "fsmitm_romfsbuild.hpp"
#include "../debug.hpp"
void RomFSBuildContext::VisitDirectory(FsFileSystem *filesys, RomFSBuildDirectoryContext *parent) {
FsDir dir;
Result rc;
std::vector<RomFSBuildDirectoryContext *> child_dirs;
/* Open the current parent directory. */
if (R_FAILED((rc = Utils::OpenRomFSDir(filesys, this->title_id, parent->path, &dir)))) {
fatalSimple(rc);
}
u64 read_entries;
while (R_SUCCEEDED((rc = fsDirRead(&dir, 0, &read_entries, 1, &this->dir_entry))) && read_entries == 1) {
if (this->dir_entry.type == ENTRYTYPE_DIR) {
RomFSBuildDirectoryContext *child = new RomFSBuildDirectoryContext({0});
/* Set child's path. */
child->cur_path_ofs = parent->path_len + 1;
child->path_len = child->cur_path_ofs + strlen(this->dir_entry.name);
child->path = new char[child->path_len + 1];
strcpy(child->path, parent->path);
if (child->path_len > FS_MAX_PATH - 1) {
fatalSimple(0xF601);
}
strcat(child->path + parent->path_len, "/");
strcat(child->path + parent->path_len, this->dir_entry.name);
if (!this->AddDirectory(parent, child, NULL)) {
delete child->path;
delete child;
} else {
child_dirs.push_back(child);
}
} else if (this->dir_entry.type == ENTRYTYPE_FILE) {
RomFSBuildFileContext *child = new RomFSBuildFileContext({0});
/* Set child's path. */
child->cur_path_ofs = parent->path_len + 1;
child->path_len = child->cur_path_ofs + strlen(this->dir_entry.name);
child->path = new char[child->path_len + 1];
strcpy(child->path, parent->path);
if (child->path_len > FS_MAX_PATH - 1) {
fatalSimple(0xF601);
}
strcat(child->path + parent->path_len, "/");
strcat(child->path + parent->path_len, this->dir_entry.name);
child->source = this->cur_source_type;
child->size = this->dir_entry.fileSize;
if (!this->AddFile(parent, child)) {
delete child->path;
delete child;
}
} else {
fatalSimple(rc);
}
}
fsDirClose(&dir);
for (auto &child : child_dirs) {
this->VisitDirectory(filesys, child);
}
}
void RomFSBuildContext::MergeSdFiles() {
FsFileSystem sd_filesystem;
FsDir dir;
if (!Utils::IsSdInitialized()) {
return;
}
if (R_FAILED((Utils::OpenSdDirForAtmosphere(this->title_id, "/romfs", &dir)))) {
return;
}
fsDirClose(&dir);
if (R_FAILED(fsMountSdcard(&sd_filesystem))) {
return;
}
this->cur_source_type = RomFSDataSource::LooseFile;
this->VisitDirectory(&sd_filesystem, this->root);
fsFsClose(&sd_filesystem);
}
void RomFSBuildContext::VisitDirectory(RomFSBuildDirectoryContext *parent, u32 parent_offset, void *dir_table, size_t dir_table_size, void *file_table, size_t file_table_size) {
RomFSDirectoryEntry *parent_entry = romfs_get_direntry(dir_table, parent_offset);
if (parent_entry->file != ROMFS_ENTRY_EMPTY) {
RomFSFileEntry *cur_file = romfs_get_fentry(file_table, parent_entry->file);
while (cur_file != NULL) {
RomFSBuildFileContext *child = new RomFSBuildFileContext({0});
/* Set child's path. */
child->cur_path_ofs = parent->path_len + 1;
child->path_len = child->cur_path_ofs + cur_file->name_size;
child->path = new char[child->path_len + 1];
strcpy(child->path, parent->path);
if (child->path_len > FS_MAX_PATH - 1) {
fatalSimple(0xF601);
}
strcat(child->path + parent->path_len, "/");
strncat(child->path + parent->path_len, cur_file->name, cur_file->name_size);
child->size = cur_file->size;
child->source = this->cur_source_type;
child->orig_offset = cur_file->offset;
if (!this->AddFile(parent, child)) {
delete child->path;
delete child;
}
if (cur_file->sibling == ROMFS_ENTRY_EMPTY) {
cur_file = NULL;
} else {
cur_file = romfs_get_fentry(file_table, cur_file->sibling);
}
}
}
if (parent_entry->child != ROMFS_ENTRY_EMPTY) {
RomFSDirectoryEntry *cur_child = romfs_get_direntry(dir_table, parent_entry->child);
u32 cur_child_offset = parent_entry->child;
while (cur_child != NULL) {
RomFSBuildDirectoryContext *child = new RomFSBuildDirectoryContext({0});
/* Set child's path. */
child->cur_path_ofs = parent->path_len + 1;
child->path_len = child->cur_path_ofs + cur_child->name_size;
child->path = new char[child->path_len + 1];
strcpy(child->path, parent->path);
if (child->path_len > FS_MAX_PATH - 1) {
fatalSimple(0xF601);
}
strcat(child->path + parent->path_len, "/");
strncat(child->path + parent->path_len, cur_child->name, cur_child->name_size);
RomFSBuildDirectoryContext *real = NULL;
if (!this->AddDirectory(parent, child, &real)) {
delete child->path;
delete child;
}
if (real == NULL) {
fatalSimple(0xF601);
}
this->VisitDirectory(real, cur_child_offset, dir_table, dir_table_size, file_table, file_table_size);
if (cur_child->sibling == ROMFS_ENTRY_EMPTY) {
cur_child = NULL;
} else {
cur_child_offset = cur_child->sibling;
cur_child = romfs_get_direntry(dir_table, cur_child->sibling);
}
}
}
}
void RomFSBuildContext::MergeRomStorage(IROStorage *storage, RomFSDataSource source) {
Result rc;
RomFSHeader header;
if (R_FAILED((rc = storage->Read(&header, sizeof(header), 0)))) {
fatalSimple(rc);
}
if (header.header_size != sizeof(header)) {
/* what */
return;
}
/* Read tables. */
auto dir_table = std::make_unique<u8[]>(header.dir_table_size);
auto file_table = std::make_unique<u8[]>(header.file_table_size);
if (R_FAILED((rc = storage->Read(dir_table.get(), header.dir_table_size, header.dir_table_ofs)))) {
fatalSimple(rc);
}
if (R_FAILED((rc = storage->Read(file_table.get(), header.file_table_size, header.file_table_ofs)))) {
fatalSimple(rc);
}
this->cur_source_type = source;
this->VisitDirectory(this->root, 0x0, dir_table.get(), (size_t)header.dir_table_size, file_table.get(), (size_t)header.file_table_size);
}
bool RomFSBuildContext::AddDirectory(RomFSBuildDirectoryContext *parent_dir_ctx, RomFSBuildDirectoryContext *dir_ctx, RomFSBuildDirectoryContext **out_dir_ctx) {
/* Check whether it's already in the known directories. */
auto existing = this->directories.find(dir_ctx->path);
if (existing != this->directories.end()) {
if (out_dir_ctx) {
*out_dir_ctx = existing->second;
}
return false;
}
/* Add a new directory. */
this->num_dirs++;
this->dir_table_size += sizeof(RomFSDirectoryEntry) + ((dir_ctx->path_len - dir_ctx->cur_path_ofs + 3) & ~3);
dir_ctx->parent = parent_dir_ctx;
this->directories.insert({dir_ctx->path, dir_ctx});
if (out_dir_ctx) {
*out_dir_ctx = dir_ctx;
}
return true;
}
bool RomFSBuildContext::AddFile(RomFSBuildDirectoryContext *parent_dir_ctx, RomFSBuildFileContext *file_ctx) {
/* Check whether it's already in the known files. */
auto existing = this->files.find(file_ctx->path);
if (existing != this->files.end()) {
return false;
}
/* Add a new file. */
this->num_files++;
this->file_table_size += sizeof(RomFSFileEntry) + ((file_ctx->path_len - file_ctx->cur_path_ofs + 3) & ~3);
file_ctx->parent = parent_dir_ctx;
this->files.insert({file_ctx->path, file_ctx});
return true;
}
void RomFSBuildContext::Build(std::vector<RomFSSourceInfo> *out_infos) {
RomFSBuildFileContext *cur_file;
RomFSBuildDirectoryContext *cur_dir;
u32 entry_offset;
u32 dir_hash_table_entry_count = romfs_get_hash_table_count(this->num_dirs);
u32 file_hash_table_entry_count = romfs_get_hash_table_count(this->num_files);
this->dir_hash_table_size = 4 * dir_hash_table_entry_count;
this->file_hash_table_size = 4 * file_hash_table_entry_count;
/* Assign metadata pointers */
RomFSHeader *header = new RomFSHeader({0});
u8 *metadata = new u8[this->dir_hash_table_size + this->dir_table_size + this->file_hash_table_size + this->file_table_size];
u32 *dir_hash_table = (u32 *)((uintptr_t)metadata);
RomFSDirectoryEntry *dir_table = (RomFSDirectoryEntry *)((uintptr_t)dir_hash_table + this->dir_hash_table_size);
u32 *file_hash_table = (u32 *)((uintptr_t)dir_table + this->dir_table_size);
RomFSFileEntry *file_table = (RomFSFileEntry *)((uintptr_t)file_hash_table + this->file_hash_table_size);
/* Clear out hash tables. */
for (u32 i = 0; i < dir_hash_table_entry_count; i++) {
dir_hash_table[i] = ROMFS_ENTRY_EMPTY;
}
for (u32 i = 0; i < file_hash_table_entry_count; i++) {
file_hash_table[i] = ROMFS_ENTRY_EMPTY;
}
out_infos->clear();
out_infos->emplace_back(0, sizeof(*header), header, RomFSDataSource::Memory);
/* Determine file offsets. */
entry_offset = 0;
RomFSBuildFileContext *prev_file = NULL;
for (const auto &it : this->files) {
cur_file = it.second;
this->file_partition_size = (this->file_partition_size + 0xFULL) & ~0xFULL;
/* Check for extra padding in the original romfs source and preserve it, to help ourselves later. */
if (prev_file && prev_file->source == cur_file->source && (prev_file->source == RomFSDataSource::BaseRomFS || prev_file->source == RomFSDataSource::FileRomFS)) {
u64 expected = (this->file_partition_size - prev_file->offset + prev_file->orig_offset);
if (expected != cur_file->orig_offset) {
if (expected > cur_file->orig_offset) {
/* This case should NEVER happen. */
fatalSimple(0xF601);
}
this->file_partition_size += cur_file->orig_offset - expected;
}
}
cur_file->offset = this->file_partition_size;
this->file_partition_size += cur_file->size;
cur_file->entry_offset = entry_offset;
entry_offset += sizeof(RomFSFileEntry) + ((cur_file->path_len - cur_file->cur_path_ofs + 3) & ~3);
prev_file = cur_file;
}
/* Assign deferred parent/sibling ownership. */
for (auto it = this->files.rbegin(); it != this->files.rend(); it++) {
cur_file = it->second;
cur_file->sibling = cur_file->parent->file;
cur_file->parent->file = cur_file;
}
/* Determine directory offsets. */
entry_offset = 0;
for (const auto &it : this->directories) {
cur_dir = it.second;
cur_dir->entry_offset = entry_offset;
entry_offset += sizeof(RomFSDirectoryEntry) + ((cur_dir->path_len - cur_dir->cur_path_ofs + 3) & ~3);
}
/* Assign deferred parent/sibling ownership. */
for (auto it = this->directories.rbegin(); it->second != this->root; it++) {
cur_dir = it->second;
cur_dir->sibling = cur_dir->parent->child;
cur_dir->parent->child = cur_dir;
}
/* Populate file tables. */
for (const auto &it : this->files) {
cur_file = it.second;
RomFSFileEntry *cur_entry = romfs_get_fentry(file_table, cur_file->entry_offset);
cur_entry->parent = cur_file->parent->entry_offset;
cur_entry->sibling = (cur_file->sibling == NULL) ? ROMFS_ENTRY_EMPTY : cur_file->sibling->entry_offset;
cur_entry->offset = cur_file->offset;
cur_entry->size = cur_file->size;
u32 name_size = cur_file->path_len - cur_file->cur_path_ofs;
u32 hash = romfs_calc_path_hash(cur_file->parent->entry_offset, (unsigned char *)cur_file->path + cur_file->cur_path_ofs, 0, name_size);
cur_entry->hash = file_hash_table[hash % file_hash_table_entry_count];
file_hash_table[hash % file_hash_table_entry_count] = cur_file->entry_offset;
cur_entry->name_size = name_size;
memset(cur_entry->name, 0, (cur_entry->name_size + 3) & ~3);
memcpy(cur_entry->name, cur_file->path + cur_file->cur_path_ofs, name_size);
switch (cur_file->source) {
case RomFSDataSource::BaseRomFS:
case RomFSDataSource::FileRomFS:
/* Try to compact, if possible. */
if (out_infos->back().type == cur_file->source) {
out_infos->back().size = cur_file->offset + ROMFS_FILEPARTITION_OFS + cur_file->size - out_infos->back().virtual_offset;
} else {
out_infos->emplace_back(cur_file->offset + ROMFS_FILEPARTITION_OFS, cur_file->size, cur_file->orig_offset + ROMFS_FILEPARTITION_OFS, cur_file->source);
}
break;
case RomFSDataSource::LooseFile:
{
char *path = new char[cur_file->path_len + 1];
strcpy(path, cur_file->path);
out_infos->emplace_back(cur_file->offset + ROMFS_FILEPARTITION_OFS, cur_file->size, path, cur_file->source);
}
break;
default:
fatalSimple(0xF601);
}
}
/* Populate dir tables. */
for (const auto &it : this->directories) {
cur_dir = it.second;
RomFSDirectoryEntry *cur_entry = romfs_get_direntry(dir_table, cur_dir->entry_offset);
cur_entry->parent = cur_dir == this->root ? 0 : cur_dir->parent->entry_offset;
cur_entry->sibling = (cur_dir->sibling == NULL) ? ROMFS_ENTRY_EMPTY : cur_dir->sibling->entry_offset;
cur_entry->child = (cur_dir->child == NULL) ? ROMFS_ENTRY_EMPTY : cur_dir->child->entry_offset;
cur_entry->file = (cur_dir->file == NULL) ? ROMFS_ENTRY_EMPTY : cur_dir->file->entry_offset;
u32 name_size = cur_dir->path_len - cur_dir->cur_path_ofs;
u32 hash = romfs_calc_path_hash(cur_dir == this->root ? 0 : cur_dir->parent->entry_offset, (unsigned char *)cur_dir->path + cur_dir->cur_path_ofs, 0, name_size);
cur_entry->hash = dir_hash_table[hash % dir_hash_table_entry_count];
dir_hash_table[hash % dir_hash_table_entry_count] = cur_dir->entry_offset;
cur_entry->name_size = name_size;
memset(cur_entry->name, 0, (cur_entry->name_size + 3) & ~3);
memcpy(cur_entry->name, cur_dir->path + cur_dir->cur_path_ofs, name_size);
}
/* Delete directories. */
for (const auto &it : this->directories) {
cur_dir = it.second;
delete cur_dir->path;
delete cur_dir;
}
this->root = NULL;
this->directories.clear();
/* Delete files. */
for (const auto &it : this->files) {
cur_file = it.second;
delete cur_file->path;
delete cur_file;
}
this->files.clear();
/* Set header fields. */
header->header_size = sizeof(*header);
header->file_hash_table_size = this->file_hash_table_size;
header->file_table_size = this->file_table_size;
header->dir_hash_table_size = this->dir_hash_table_size;
header->dir_table_size = this->dir_table_size;
header->file_partition_ofs = ROMFS_FILEPARTITION_OFS;
header->dir_hash_table_ofs = (header->file_partition_ofs + this->file_partition_size + 3ULL) & ~3ULL;
header->dir_table_ofs = header->dir_hash_table_ofs + header->dir_hash_table_size;
header->file_hash_table_ofs = header->dir_table_ofs + header->dir_table_size;
header->file_table_ofs = header->file_hash_table_ofs + header->file_hash_table_size;
const size_t metadata_size = this->dir_hash_table_size + this->dir_table_size + this->file_hash_table_size + this->file_table_size;
/* Try to save metadata to the SD card, to save on memory space. */
if (R_SUCCEEDED(Utils::SaveSdFileForAtmosphere(this->title_id, ROMFS_METADATA_FILE_PATH, metadata, metadata_size))) {
out_infos->emplace_back(header->dir_hash_table_ofs, metadata_size, RomFSDataSource::MetaData);
delete metadata;
} else {
out_infos->emplace_back(header->dir_hash_table_ofs, metadata_size, metadata, RomFSDataSource::Memory);
}
}

View File

@@ -0,0 +1,280 @@
/*
* Copyright (c) 2018 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 <map>
#include "fsmitm_romstorage.hpp"
#include "../debug.hpp"
#define ROMFS_ENTRY_EMPTY 0xFFFFFFFF
#define ROMFS_FILEPARTITION_OFS 0x200
#define ROMFS_METADATA_FILE_PATH "romfs_metadata.bin"
/* Types for RomFS Meta construction. */
enum class RomFSDataSource {
BaseRomFS,
FileRomFS,
LooseFile,
MetaData,
Memory,
};
struct RomFSBaseSourceInfo {
u64 offset;
};
struct RomFSFileSourceInfo {
u64 offset;
};
struct RomFSLooseSourceInfo {
const char *path;
};
struct RomFSMemorySourceInfo {
const u8 *data;
};
struct RomFSMetaDataSourceInfo {
};
struct RomFSSourceInfo {
u64 virtual_offset;
u64 size;
union {
RomFSBaseSourceInfo base_source_info;
RomFSFileSourceInfo file_source_info;
RomFSLooseSourceInfo loose_source_info;
RomFSMemorySourceInfo memory_source_info;
RomFSMemorySourceInfo metadata_source_info;
};
RomFSDataSource type;
RomFSSourceInfo(u64 v_o, u64 s, u64 offset, RomFSDataSource t) : virtual_offset(v_o), size(s), type(t) {
switch (this->type) {
case RomFSDataSource::BaseRomFS:
this->base_source_info.offset = offset;
break;
case RomFSDataSource::FileRomFS:
this->file_source_info.offset = offset;
break;
case RomFSDataSource::LooseFile:
case RomFSDataSource::MetaData:
case RomFSDataSource::Memory:
default:
fatalSimple(0xF601);
}
}
RomFSSourceInfo(u64 v_o, u64 s, const void *arg, RomFSDataSource t) : virtual_offset(v_o), size(s), type(t) {
switch (this->type) {
case RomFSDataSource::LooseFile:
this->loose_source_info.path = (decltype(this->loose_source_info.path))arg;
break;
case RomFSDataSource::Memory:
this->memory_source_info.data = (decltype(this->memory_source_info.data))arg;
break;
case RomFSDataSource::MetaData:
case RomFSDataSource::BaseRomFS:
case RomFSDataSource::FileRomFS:
default:
fatalSimple(0xF601);
}
}
RomFSSourceInfo(u64 v_o, u64 s, RomFSDataSource t) : virtual_offset(v_o), size(s), type(t) {
switch (this->type) {
case RomFSDataSource::MetaData:
break;
case RomFSDataSource::LooseFile:
case RomFSDataSource::Memory:
case RomFSDataSource::BaseRomFS:
case RomFSDataSource::FileRomFS:
default:
fatalSimple(0xF601);
}
}
void Cleanup() {
switch (this->type) {
case RomFSDataSource::BaseRomFS:
case RomFSDataSource::FileRomFS:
case RomFSDataSource::MetaData:
break;
case RomFSDataSource::LooseFile:
delete this->loose_source_info.path;
break;
case RomFSDataSource::Memory:
delete this->memory_source_info.data;
break;
default:
fatalSimple(0xF601);
}
}
static bool Compare(RomFSSourceInfo *a, RomFSSourceInfo *b) {
return (a->virtual_offset < b->virtual_offset);
}
};
/* Types for building a RomFS. */
struct RomFSHeader {
u64 header_size;
u64 dir_hash_table_ofs;
u64 dir_hash_table_size;
u64 dir_table_ofs;
u64 dir_table_size;
u64 file_hash_table_ofs;
u64 file_hash_table_size;
u64 file_table_ofs;
u64 file_table_size;
u64 file_partition_ofs;
};
static_assert(sizeof(RomFSHeader) == 0x50, "Incorrect RomFS Header definition!");
struct RomFSDirectoryEntry {
u32 parent;
u32 sibling;
u32 child;
u32 file;
u32 hash;
u32 name_size;
char name[];
};
static_assert(sizeof(RomFSDirectoryEntry) == 0x18, "Incorrect RomFSDirectoryEntry definition!");
struct RomFSFileEntry {
u32 parent;
u32 sibling;
u64 offset;
u64 size;
u32 hash;
u32 name_size;
char name[];
};
static_assert(sizeof(RomFSFileEntry) == 0x20, "Incorrect RomFSFileEntry definition!");
struct RomFSBuildFileContext;
/* Used as comparator for std::map<char *, RomFSBuild*Context> */
struct build_ctx_cmp {
bool operator()(const char *a, const char *b) const {
return strcmp(a, b) < 0;
}
};
struct RomFSBuildDirectoryContext {
char *path;
u32 cur_path_ofs;
u32 path_len;
u32 entry_offset = 0;
RomFSBuildDirectoryContext *parent = NULL;
RomFSBuildDirectoryContext *child = NULL;
RomFSBuildDirectoryContext *sibling = NULL;
RomFSBuildFileContext *file = NULL;
};
struct RomFSBuildFileContext {
char *path;
u32 cur_path_ofs;
u32 path_len;
u32 entry_offset = 0;
u64 offset = 0;
u64 size = 0;
RomFSBuildDirectoryContext *parent = NULL;
RomFSBuildFileContext *sibling = NULL;
RomFSDataSource source{0};
u64 orig_offset = 0;
};
class RomFSBuildContext {
private:
u64 title_id;
RomFSBuildDirectoryContext *root;
std::map<char *, RomFSBuildDirectoryContext *, build_ctx_cmp> directories;
std::map<char *, RomFSBuildFileContext *, build_ctx_cmp> files;
u64 num_dirs = 0;
u64 num_files = 0;
u64 dir_table_size = 0;
u64 file_table_size = 0;
u64 dir_hash_table_size = 0;
u64 file_hash_table_size = 0;
u64 file_partition_size = 0;
FsDirectoryEntry dir_entry;
RomFSDataSource cur_source_type;
void VisitDirectory(FsFileSystem *filesys, RomFSBuildDirectoryContext *parent);
void VisitDirectory(RomFSBuildDirectoryContext *parent, u32 parent_offset, void *dir_table, size_t dir_table_size, void *file_table, size_t file_table_size);
bool AddDirectory(RomFSBuildDirectoryContext *parent_dir_ctx, RomFSBuildDirectoryContext *dir_ctx, RomFSBuildDirectoryContext **out_dir_ctx);
bool AddFile(RomFSBuildDirectoryContext *parent_dir_ctx, RomFSBuildFileContext *file_ctx);
public:
RomFSBuildContext(u64 tid) : title_id(tid) {
this->root = new RomFSBuildDirectoryContext({0});
this->root->path = new char[1];
this->root->path[0] = '\x00';
this->directories.insert({this->root->path, this->root});
this->num_dirs = 1;
this->dir_table_size = 0x18;
}
void MergeSdFiles();
void MergeRomStorage(IROStorage *storage, RomFSDataSource source);
/* This finalizes the context. */
void Build(std::vector<RomFSSourceInfo> *out_infos);
};
static inline RomFSDirectoryEntry *romfs_get_direntry(void *directories, uint32_t offset) {
return (RomFSDirectoryEntry *)((uintptr_t)directories + offset);
}
static inline RomFSFileEntry *romfs_get_fentry(void *files, uint32_t offset) {
return (RomFSFileEntry *)((uintptr_t)files + offset);
}
static inline uint32_t romfs_calc_path_hash(uint32_t parent, const unsigned char *path, uint32_t start, size_t path_len) {
uint32_t hash = parent ^ 123456789;
for (uint32_t i = 0; i < path_len; i++) {
hash = (hash >> 5) | (hash << 27);
hash ^= path[start + i];
}
return hash;
}
static inline uint32_t romfs_get_hash_table_count(uint32_t num_entries) {
if (num_entries < 3) {
return 3;
} else if (num_entries < 19) {
return num_entries | 1;
}
uint32_t count = num_entries;
while (count % 2 == 0 || count % 3 == 0 || count % 5 == 0 || count % 7 == 0 || count % 11 == 0 || count % 13 == 0 || count % 17 == 0) {
count++;
}
return count;
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright (c) 2018 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_istorage.hpp"
/* Represents a RomFS stored in some file. */
class RomFileStorage : public IROStorage {
private:
FsFile *base_file;
public:
RomFileStorage(FsFile *f) : base_file(f) {
/* ... */
};
RomFileStorage(FsFile f) {
this->base_file = new FsFile(f);
};
virtual ~RomFileStorage() {
fsFileClose(base_file);
delete base_file;
};
public:
Result Read(void *buffer, size_t size, u64 offset) override {
size_t out_sz = 0;
Result rc = fsFileRead(this->base_file, offset, buffer, size, &out_sz);
if (R_SUCCEEDED(rc) && out_sz != size && out_sz) {
return this->Read((void *)((uintptr_t)buffer + out_sz), size - out_sz, offset + out_sz);
}
return rc;
};
Result GetSize(u64 *out_size) override {
return fsFileGetSize(this->base_file, out_size);
};
Result OperateRange(u32 operation_type, u64 offset, u64 size, FsRangeInfo *out_range_info) override {
/* TODO: Merge into libnx? */
return fsFileOperateRange(this->base_file, operation_type, offset, size, out_range_info);
};
};
/* Represents a RomFS accessed via some IStorage. */
using RomInterfaceStorage = ROProxyStorage;

View File

@@ -0,0 +1,267 @@
/*
* Copyright (c) 2018 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/>.
*/
#include <map>
#include <memory>
#include <mutex>
#include <switch.h>
#include <stratosphere.hpp>
#include "fsmitm_service.hpp"
#include "fs_shim.h"
#include "../utils.hpp"
#include "fsmitm_boot0storage.hpp"
#include "fsmitm_romstorage.hpp"
#include "fsmitm_layeredrom.hpp"
#include "../debug.hpp"
static HosMutex g_StorageCacheLock;
static std::unordered_map<u64, std::weak_ptr<IStorageInterface>> g_StorageCache;
static bool StorageCacheGetEntry(u64 title_id, std::shared_ptr<IStorageInterface> *out) {
std::scoped_lock<HosMutex> lock(g_StorageCacheLock);
if (g_StorageCache.find(title_id) == g_StorageCache.end()) {
return false;
}
auto intf = g_StorageCache[title_id].lock();
if (intf != nullptr) {
*out = intf;
return true;
}
return false;
}
static void StorageCacheSetEntry(u64 title_id, std::shared_ptr<IStorageInterface> *ptr) {
std::scoped_lock<HosMutex> lock(g_StorageCacheLock);
/* Ensure we always use the cached copy if present. */
if (g_StorageCache.find(title_id) != g_StorageCache.end()) {
auto intf = g_StorageCache[title_id].lock();
if (intf != nullptr) {
*ptr = intf;
}
}
g_StorageCache[title_id] = *ptr;
}
void FsMitmService::PostProcess(IMitmServiceObject *obj, IpcResponseContext *ctx) {
auto this_ptr = static_cast<FsMitmService *>(obj);
switch ((FspSrvCmd)ctx->cmd_id) {
case FspSrvCmd_SetCurrentProcess:
if (R_SUCCEEDED(ctx->rc)) {
this_ptr->has_initialized = true;
this_ptr->process_id = ctx->request.Pid;
this_ptr->title_id = this_ptr->process_id;
if (R_FAILED(MitmQueryUtils::GetAssociatedTidForPid(this_ptr->process_id, &this_ptr->title_id))) {
/* Log here, if desired. */
}
break;
}
break;
default:
break;
}
}
/* Gate access to the BIS partitions. */
Result FsMitmService::OpenBisStorage(Out<std::shared_ptr<IStorageInterface>> out_storage, u32 bis_partition_id) {
std::shared_ptr<IStorageInterface> storage = nullptr;
u32 out_domain_id = 0;
Result rc = 0;
ON_SCOPE_EXIT {
if (R_SUCCEEDED(rc)) {
out_storage.SetValue(std::move(storage));
if (out_storage.IsDomain()) {
out_storage.ChangeObjectId(out_domain_id);
}
}
};
{
FsStorage bis_storage;
rc = fsOpenBisStorageFwd(this->forward_service.get(), &bis_storage, bis_partition_id);
if (R_SUCCEEDED(rc)) {
const bool is_sysmodule = this->title_id < 0x0100000000001000;
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");
if (bis_partition_id == BisStorageId_Boot0) {
storage = std::make_shared<IStorageInterface>(new Boot0Storage(bis_storage, this->title_id));
} else if (bis_partition_id == BisStorageId_Prodinfo) {
/* PRODINFO should *never* be writable. */
if (is_sysmodule || has_cal0_read_flag) {
storage = std::make_shared<IStorageInterface>(new ROProxyStorage(bis_storage));
} else {
/* Do not allow non-sysmodules to read *or* write CAL0. */
fsStorageClose(&bis_storage);
return 0x320002;
}
} else {
if (is_sysmodule || has_bis_write_flag) {
/* Sysmodules should still be allowed to read and write. */
storage = std::make_shared<IStorageInterface>(new ProxyStorage(bis_storage));
} else {
/* Non-sysmodules should be allowed to read. */
storage = std::make_shared<IStorageInterface>(new ROProxyStorage(bis_storage));
}
}
if (out_storage.IsDomain()) {
out_domain_id = bis_storage.s.object_id;
}
}
}
return rc;
}
/* Add redirection for RomFS to the SD card. */
Result FsMitmService::OpenDataStorageByCurrentProcess(Out<std::shared_ptr<IStorageInterface>> out_storage) {
std::shared_ptr<IStorageInterface> storage = nullptr;
u32 out_domain_id = 0;
Result rc = 0;
if (!this->should_override_contents) {
return RESULT_FORWARD_TO_SESSION;
}
bool has_cache = StorageCacheGetEntry(this->title_id, &storage);
ON_SCOPE_EXIT {
if (R_SUCCEEDED(rc)) {
if (!has_cache) {
StorageCacheSetEntry(this->title_id, &storage);
}
out_storage.SetValue(std::move(storage));
if (out_storage.IsDomain()) {
out_storage.ChangeObjectId(out_domain_id);
}
}
};
if (has_cache) {
if (out_storage.IsDomain()) {
FsStorage s = {0};
rc = fsOpenDataStorageByCurrentProcessFwd(this->forward_service.get(), &s);
if (R_SUCCEEDED(rc)) {
out_domain_id = s.s.object_id;
}
} else {
rc = 0;
}
if (R_FAILED(rc)) {
storage.reset();
}
} else {
FsStorage data_storage;
FsFile data_file;
rc = fsOpenDataStorageByCurrentProcessFwd(this->forward_service.get(), &data_storage);
Log(armGetTls(), 0x100);
if (R_SUCCEEDED(rc)) {
if (Utils::HasSdRomfsContent(this->title_id)) {
/* TODO: Is there a sensible path that ends in ".romfs" we can use?" */
if (R_SUCCEEDED(Utils::OpenSdFileForAtmosphere(this->title_id, "romfs.bin", FS_OPEN_READ, &data_file))) {
storage = std::make_shared<IStorageInterface>(new LayeredRomFS(std::make_shared<RomInterfaceStorage>(data_storage), std::make_shared<RomFileStorage>(data_file), this->title_id));
} else {
storage = std::make_shared<IStorageInterface>(new LayeredRomFS(std::make_shared<RomInterfaceStorage>(data_storage), nullptr, this->title_id));
}
if (out_storage.IsDomain()) {
out_domain_id = data_storage.s.object_id;
}
} else {
/* If we don't have anything to modify, there's no sense in maintaining a copy of the metadata tables. */
fsStorageClose(&data_storage);
rc = RESULT_FORWARD_TO_SESSION;
}
}
}
return rc;
}
/* Add redirection for System Data Archives to the SD card. */
Result FsMitmService::OpenDataStorageByDataId(Out<std::shared_ptr<IStorageInterface>> out_storage, u64 data_id, u8 sid) {
FsStorageId storage_id = (FsStorageId)sid;
FsStorage data_storage;
FsFile data_file;
if (!this->should_override_contents) {
return RESULT_FORWARD_TO_SESSION;
}
std::shared_ptr<IStorageInterface> storage = nullptr;
u32 out_domain_id = 0;
Result rc = 0;
bool has_cache = StorageCacheGetEntry(data_id, &storage);
ON_SCOPE_EXIT {
if (R_SUCCEEDED(rc)) {
if (!has_cache) {
StorageCacheSetEntry(data_id, &storage);
}
out_storage.SetValue(std::move(storage));
if (out_storage.IsDomain()) {
out_storage.ChangeObjectId(out_domain_id);
}
}
};
if (has_cache) {
if (out_storage.IsDomain()) {
FsStorage s = {0};
rc = fsOpenDataStorageByDataIdFwd(this->forward_service.get(), storage_id, data_id, &s);
if (R_SUCCEEDED(rc)) {
out_domain_id = s.s.object_id;
}
} else {
rc = 0;
}
if (R_FAILED(rc)) {
storage.reset();
}
} else {
rc = fsOpenDataStorageByDataIdFwd(this->forward_service.get(), storage_id, data_id, &data_storage);
if (R_SUCCEEDED(rc)) {
if (Utils::HasSdRomfsContent(data_id)) {
/* TODO: Is there a sensible path that ends in ".romfs" we can use?" */
if (R_SUCCEEDED(Utils::OpenSdFileForAtmosphere(data_id, "romfs.bin", FS_OPEN_READ, &data_file))) {
storage = std::make_shared<IStorageInterface>(new LayeredRomFS(std::make_shared<RomInterfaceStorage>(data_storage), std::make_shared<RomFileStorage>(data_file), data_id));
} else {
storage = std::make_shared<IStorageInterface>(new LayeredRomFS(std::make_shared<RomInterfaceStorage>(data_storage), nullptr, data_id));
}
if (out_storage.IsDomain()) {
out_domain_id = data_storage.s.object_id;
}
} else {
/* If we don't have anything to modify, there's no sense in maintaining a copy of the metadata tables. */
fsStorageClose(&data_storage);
rc = RESULT_FORWARD_TO_SESSION;
}
}
}
return rc;
}

View File

@@ -0,0 +1,73 @@
/*
* Copyright (c) 2018 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_istorage.hpp"
#include "../utils.hpp"
enum FspSrvCmd : u32 {
FspSrvCmd_SetCurrentProcess = 1,
FspSrvCmd_OpenBisStorage = 12,
FspSrvCmd_OpenDataStorageByCurrentProcess = 200,
FspSrvCmd_OpenDataStorageByDataId = 202,
};
class FsMitmService : public IMitmServiceObject {
private:
bool has_initialized = false;
bool should_override_contents;
public:
FsMitmService(std::shared_ptr<Service> s, u64 pid) : IMitmServiceObject(s, pid) {
if (Utils::HasSdDisableMitMFlag(this->title_id)) {
this->should_override_contents = false;
} else {
this->should_override_contents = (this->title_id >= 0x0100000000010000ULL || Utils::HasSdMitMFlag(this->title_id)) && Utils::HasOverrideButton(this->title_id);
}
}
static bool ShouldMitm(u64 pid, u64 tid) {
/* Don't Mitm KIPs */
if (pid < 0x50) {
return false;
}
static std::atomic_bool has_launched_qlaunch = false;
/* TODO: intercepting everything seems to cause issues with sleep mode, for some reason. */
/* Figure out why, and address it. */
if (tid == 0x0100000000001000ULL) {
has_launched_qlaunch = true;
}
return has_launched_qlaunch || tid == 0x010000000000001FULL || tid >= 0x0100000000010000ULL || Utils::HasSdMitMFlag(tid);
}
static void PostProcess(IMitmServiceObject *obj, IpcResponseContext *ctx);
protected:
/* Overridden commands. */
Result OpenBisStorage(Out<std::shared_ptr<IStorageInterface>> out, u32 bis_partition_id);
Result OpenDataStorageByCurrentProcess(Out<std::shared_ptr<IStorageInterface>> out);
Result OpenDataStorageByDataId(Out<std::shared_ptr<IStorageInterface>> out, u64 data_id, u8 storage_id);
public:
DEFINE_SERVICE_DISPATCH_TABLE {
MakeServiceCommandMeta<FspSrvCmd_OpenBisStorage, &FsMitmService::OpenBisStorage>(),
MakeServiceCommandMeta<FspSrvCmd_OpenDataStorageByCurrentProcess, &FsMitmService::OpenDataStorageByCurrentProcess>(),
MakeServiceCommandMeta<FspSrvCmd_OpenDataStorageByDataId, &FsMitmService::OpenDataStorageByDataId>(),
};
};

View File

@@ -0,0 +1,269 @@
/* inih -- simple .INI file parser
inih is released under the New BSD license (see LICENSE.txt). Go to the project
home page for more info:
https://github.com/benhoyt/inih
*/
#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
#define _CRT_SECURE_NO_WARNINGS
#endif
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include "ini.h"
#if !INI_USE_STACK
#include <stdlib.h>
#endif
#define MAX_SECTION 72
#define MAX_NAME 72
/* Used by ini_parse_string() to keep track of string parsing state. */
typedef struct {
const char* ptr;
size_t num_left;
} ini_parse_string_ctx;
/* Strip whitespace chars off end of given string, in place. Return s. */
static char* rstrip(char* s)
{
char* p = s + strlen(s);
while (p > s && isspace((unsigned char)(*--p)))
*p = '\0';
return s;
}
/* Return pointer to first non-whitespace char in given string. */
static char* lskip(const char* s)
{
while (*s && isspace((unsigned char)(*s)))
s++;
return (char*)s;
}
/* Return pointer to first char (of chars) or inline comment in given string,
or pointer to null at end of string if neither found. Inline comment must
be prefixed by a whitespace character to register as a comment. */
static char* find_chars_or_comment(const char* s, const char* chars)
{
#if INI_ALLOW_INLINE_COMMENTS
int was_space = 0;
while (*s && (!chars || !strchr(chars, *s)) &&
!(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) {
was_space = isspace((unsigned char)(*s));
s++;
}
#else
while (*s && (!chars || !strchr(chars, *s))) {
s++;
}
#endif
return (char*)s;
}
/* Version of strncpy that ensures dest (size bytes) is null-terminated. */
static char* strncpy0(char* dest, const char* src, size_t size)
{
strncpy(dest, src, size - 1);
dest[size - 1] = '\0';
return dest;
}
/* See documentation in header file. */
int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler,
void* user)
{
/* Uses a fair bit of stack (use heap instead if you need to) */
#if INI_USE_STACK
char line[INI_MAX_LINE];
int max_line = INI_MAX_LINE;
#else
char* line;
int max_line = INI_INITIAL_ALLOC;
#endif
#if INI_ALLOW_REALLOC
char* new_line;
int offset;
#endif
char section[MAX_SECTION] = "";
char prev_name[MAX_NAME] = "";
char* start;
char* end;
char* name;
char* value;
int lineno = 0;
int error = 0;
#if !INI_USE_STACK
line = (char*)malloc(INI_INITIAL_ALLOC);
if (!line) {
return -2;
}
#endif
#if INI_HANDLER_LINENO
#define HANDLER(u, s, n, v) handler(u, s, n, v, lineno)
#else
#define HANDLER(u, s, n, v) handler(u, s, n, v)
#endif
/* Scan through stream line by line */
while (reader(line, max_line, stream) != NULL) {
#if INI_ALLOW_REALLOC
offset = strlen(line);
while (offset == max_line - 1 && line[offset - 1] != '\n') {
max_line *= 2;
if (max_line > INI_MAX_LINE)
max_line = INI_MAX_LINE;
new_line = realloc(line, max_line);
if (!new_line) {
free(line);
return -2;
}
line = new_line;
if (reader(line + offset, max_line - offset, stream) == NULL)
break;
if (max_line >= INI_MAX_LINE)
break;
offset += strlen(line + offset);
}
#endif
lineno++;
start = line;
#if INI_ALLOW_BOM
if (lineno == 1 && (unsigned char)start[0] == 0xEF &&
(unsigned char)start[1] == 0xBB &&
(unsigned char)start[2] == 0xBF) {
start += 3;
}
#endif
start = lskip(rstrip(start));
if (strchr(INI_START_COMMENT_PREFIXES, *start)) {
/* Start-of-line comment */
}
#if INI_ALLOW_MULTILINE
else if (*prev_name && *start && start > line) {
/* Non-blank line with leading whitespace, treat as continuation
of previous name's value (as per Python configparser). */
if (!HANDLER(user, section, prev_name, start) && !error)
error = lineno;
}
#endif
else if (*start == '[') {
/* A "[section]" line */
end = find_chars_or_comment(start + 1, "]");
if (*end == ']') {
*end = '\0';
strncpy0(section, start + 1, sizeof(section));
*prev_name = '\0';
}
else if (!error) {
/* No ']' found on section line */
error = lineno;
}
}
else if (*start) {
/* Not a comment, must be a name[=:]value pair */
end = find_chars_or_comment(start, "=:");
if (*end == '=' || *end == ':') {
*end = '\0';
name = rstrip(start);
value = end + 1;
#if INI_ALLOW_INLINE_COMMENTS
end = find_chars_or_comment(value, NULL);
if (*end)
*end = '\0';
#endif
value = lskip(value);
rstrip(value);
/* Valid name[=:]value pair found, call handler */
strncpy0(prev_name, name, sizeof(prev_name));
if (!HANDLER(user, section, name, value) && !error)
error = lineno;
}
else if (!error) {
/* No '=' or ':' found on name[=:]value line */
error = lineno;
}
}
#if INI_STOP_ON_FIRST_ERROR
if (error)
break;
#endif
}
#if !INI_USE_STACK
free(line);
#endif
return error;
}
/* See documentation in header file. */
int ini_parse_file(FILE* file, ini_handler handler, void* user)
{
return ini_parse_stream((ini_reader)fgets, file, handler, user);
}
/* See documentation in header file. */
int ini_parse(const char* filename, ini_handler handler, void* user)
{
FILE* file;
int error;
file = fopen(filename, "r");
if (!file)
return -1;
error = ini_parse_file(file, handler, user);
fclose(file);
return error;
}
/* An ini_reader function to read the next line from a string buffer. This
is the fgets() equivalent used by ini_parse_string(). */
static char* ini_reader_string(char* str, int num, void* stream) {
ini_parse_string_ctx* ctx = (ini_parse_string_ctx*)stream;
const char* ctx_ptr = ctx->ptr;
size_t ctx_num_left = ctx->num_left;
char* strp = str;
char c;
if (ctx_num_left == 0 || num < 2)
return NULL;
while (num > 1 && ctx_num_left != 0) {
c = *ctx_ptr++;
ctx_num_left--;
*strp++ = c;
if (c == '\n')
break;
num--;
}
*strp = '\0';
ctx->ptr = ctx_ptr;
ctx->num_left = ctx_num_left;
return str;
}
/* See documentation in header file. */
int ini_parse_string(const char* string, ini_handler handler, void* user) {
ini_parse_string_ctx ctx;
ctx.ptr = string;
ctx.num_left = strlen(string);
return ini_parse_stream((ini_reader)ini_reader_string, &ctx, handler,
user);
}

View File

@@ -0,0 +1,130 @@
/* inih -- simple .INI file parser
inih is released under the New BSD license (see LICENSE.txt). Go to the project
home page for more info:
https://github.com/benhoyt/inih
*/
#ifndef __INI_H__
#define __INI_H__
/* Make this header file easier to include in C++ code */
#ifdef __cplusplus
extern "C" {
#endif
#include <stdio.h>
/* Nonzero if ini_handler callback should accept lineno parameter. */
#ifndef INI_HANDLER_LINENO
#define INI_HANDLER_LINENO 0
#endif
/* Typedef for prototype of handler function. */
#if INI_HANDLER_LINENO
typedef int (*ini_handler)(void* user, const char* section,
const char* name, const char* value,
int lineno);
#else
typedef int (*ini_handler)(void* user, const char* section,
const char* name, const char* value);
#endif
/* Typedef for prototype of fgets-style reader function. */
typedef char* (*ini_reader)(char* str, int num, void* stream);
/* Parse given INI-style file. May have [section]s, name=value pairs
(whitespace stripped), and comments starting with ';' (semicolon). Section
is "" if name=value pair parsed before any section heading. name:value
pairs are also supported as a concession to Python's configparser.
For each name=value pair parsed, call handler function with given user
pointer as well as section, name, and value (data only valid for duration
of handler call). Handler should return nonzero on success, zero on error.
Returns 0 on success, line number of first error on parse error (doesn't
stop on first error), -1 on file open error, or -2 on memory allocation
error (only when INI_USE_STACK is zero).
*/
int ini_parse(const char* filename, ini_handler handler, void* user);
/* Same as ini_parse(), but takes a FILE* instead of filename. This doesn't
close the file when it's finished -- the caller must do that. */
int ini_parse_file(FILE* file, ini_handler handler, void* user);
/* Same as ini_parse(), but takes an ini_reader function pointer instead of
filename. Used for implementing custom or string-based I/O (see also
ini_parse_string). */
int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler,
void* user);
/* Same as ini_parse(), but takes a zero-terminated string with the INI data
instead of a file. Useful for parsing INI data from a network socket or
already in memory. */
int ini_parse_string(const char* string, ini_handler handler, void* user);
/* Nonzero to allow multi-line value parsing, in the style of Python's
configparser. If allowed, ini_parse() will call the handler with the same
name for each subsequent line parsed. */
#ifndef INI_ALLOW_MULTILINE
#define INI_ALLOW_MULTILINE 1
#endif
/* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of
the file. See http://code.google.com/p/inih/issues/detail?id=21 */
#ifndef INI_ALLOW_BOM
#define INI_ALLOW_BOM 1
#endif
/* Chars that begin a start-of-line comment. Per Python configparser, allow
both ; and # comments at the start of a line by default. */
#ifndef INI_START_COMMENT_PREFIXES
#define INI_START_COMMENT_PREFIXES ";#"
#endif
/* Nonzero to allow inline comments (with valid inline comment characters
specified by INI_INLINE_COMMENT_PREFIXES). Set to 0 to turn off and match
Python 3.2+ configparser behaviour. */
#ifndef INI_ALLOW_INLINE_COMMENTS
#define INI_ALLOW_INLINE_COMMENTS 1
#endif
#ifndef INI_INLINE_COMMENT_PREFIXES
#define INI_INLINE_COMMENT_PREFIXES ";"
#endif
/* Nonzero to use stack for line buffer, zero to use heap (malloc/free). */
#ifndef INI_USE_STACK
#define INI_USE_STACK 1
#endif
/* Maximum line length for any line in INI file (stack or heap). Note that
this must be 3 more than the longest line (due to '\r', '\n', and '\0'). */
#ifndef INI_MAX_LINE
#define INI_MAX_LINE 200
#endif
/* Nonzero to allow heap line buffer to grow via realloc(), zero for a
fixed-size buffer of INI_MAX_LINE bytes. Only applies if INI_USE_STACK is
zero. */
#ifndef INI_ALLOW_REALLOC
#define INI_ALLOW_REALLOC 0
#endif
/* Initial size in bytes for heap line buffer. Only applies if INI_USE_STACK
is zero. */
#ifndef INI_INITIAL_ALLOC
#define INI_INITIAL_ALLOC 200
#endif
/* Stop parsing on first error (default is to keep parsing). */
#ifndef INI_STOP_ON_FIRST_ERROR
#define INI_STOP_ON_FIRST_ERROR 0
#endif
#ifdef __cplusplus
}
#endif
#endif /* __INI_H__ */

View File

@@ -0,0 +1,59 @@
/*
* Copyright (c) 2018 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/>.
*/
#include <cstdlib>
#include <cstdint>
#include <cstring>
#include <malloc.h>
#include <switch.h>
#include <atmosphere.h>
#include <stratosphere.hpp>
#include "setmitm_main.hpp"
#include "setsys_mitm_service.hpp"
#include "setsys_settings_items.hpp"
#include "../utils.hpp"
struct SetSysManagerOptions {
static const size_t PointerBufferSize = 0x100;
static const size_t MaxDomains = 4;
static const size_t MaxDomainObjects = 0x100;
};
using SetMitmManager = WaitableManager<SetSysManagerOptions>;
void SetMitmMain(void *arg) {
/* Wait for SD to initialize. */
Utils::WaitSdInitialized();
/* Load settings */
SettingsItemManager::LoadConfiguration();
/* Create server manager */
auto server_manager = new SetMitmManager(3);
/* Create set:sys mitm. */
AddMitmServerToManager<SetSysMitmService>(server_manager, "set:sys", 60);
/* Loop forever, servicing our services. */
server_manager->Process();
delete server_manager;
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright (c) 2018 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/>.
*/
#include <cstdlib>
#include <cstdint>
#include <cstring>
#include <malloc.h>
#include <switch.h>
#include <atmosphere.h>
#include <stratosphere.hpp>
constexpr u32 SetMitmPriority = 43;
constexpr u32 SetMitmStackSize = 0x8000;
void SetMitmMain(void *arg);

View File

@@ -0,0 +1,55 @@
/*
* Copyright (c) 2018 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/>.
*/
#include <mutex>
#include <switch.h>
#include "setsys_firmware_version.hpp"
static HosMutex g_version_mutex;
static bool g_got_version = false;
static SetSysFirmwareVersion g_fw_version = {0};
Result VersionManager::GetFirmwareVersion(u64 title_id, SetSysFirmwareVersion *out) {
std::scoped_lock<HosMutex> lock(g_version_mutex);
if (!g_got_version) {
Result rc = setsysGetFirmwareVersion(&g_fw_version);
if (R_FAILED(rc)) {
return rc;
}
/* Modify the output firmware version. */
{
u32 major, minor, micro;
char display_version[sizeof(g_fw_version.display_version)] = {0};
GetAtmosphereApiVersion(&major, &minor, &micro, nullptr, nullptr);
snprintf(display_version, sizeof(display_version), "%s (AMS %u.%u.%u)", g_fw_version.display_version, major, minor, micro);
memcpy(g_fw_version.display_version, display_version, sizeof(g_fw_version.display_version));
}
g_got_version = true;
}
/* Report atmosphere string to qlaunch, maintenance and nothing else. */
if (title_id == 0x0100000000001000ULL || title_id == 0x0100000000001015ULL) {
*out = g_fw_version;
return 0;
} else {
return setsysGetFirmwareVersion(out);
}
}

View File

@@ -0,0 +1,24 @@
/*
* Copyright (c) 2018 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>
class VersionManager {
public:
static Result GetFirmwareVersion(u64 title_id, SetSysFirmwareVersion *out);
};

View File

@@ -0,0 +1,119 @@
/*
* Copyright (c) 2018 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/>.
*/
#include <mutex>
#include <algorithm>
#include <switch.h>
#include "setsys_mitm_service.hpp"
#include "setsys_firmware_version.hpp"
#include "setsys_settings_items.hpp"
void SetSysMitmService::PostProcess(IMitmServiceObject *obj, IpcResponseContext *ctx) {
/* No commands need postprocessing. */
}
Result SetSysMitmService::GetFirmwareVersion(OutPointerWithServerSize<SetSysFirmwareVersion, 0x1> out) {
Result rc = VersionManager::GetFirmwareVersion(this->title_id, out.pointer);
/* GetFirmwareVersion sanitizes these fields. */
if (R_SUCCEEDED(rc)) {
out.pointer->revision_major = 0;
out.pointer->revision_minor = 0;
}
return rc;
}
Result SetSysMitmService::GetFirmwareVersion2(OutPointerWithServerSize<SetSysFirmwareVersion, 0x1> out) {
return VersionManager::GetFirmwareVersion(this->title_id, out.pointer);
}
Result SetSysMitmService::GetSettingsItemValueSize(Out<u64> out_size, InPointer<char> in_name, InPointer<char> in_key) {
char name[SET_MAX_NAME_SIZE] = {0};
char key[SET_MAX_NAME_SIZE] = {0};
Result rc = SettingsItemManager::ValidateName(in_name.pointer);
if (R_FAILED(rc)) {
return rc;
}
rc = SettingsItemManager::ValidateKey(in_key.pointer);
if (R_FAILED(rc)) {
return rc;
}
if (in_name.num_elements < SET_MAX_NAME_SIZE) {
strncpy(name, in_name.pointer, in_name.num_elements);
} else {
strncpy(name, in_name.pointer, SET_MAX_NAME_SIZE-1);
}
if (in_key.num_elements < SET_MAX_NAME_SIZE) {
strncpy(key, in_key.pointer, in_key.num_elements);
} else {
strncpy(key, in_key.pointer, SET_MAX_NAME_SIZE-1);
}
rc = SettingsItemManager::GetValueSize(name, key, out_size.GetPointer());
if (R_FAILED(rc)) {
rc = setsysGetSettingsItemValueSize(name, key, out_size.GetPointer());
}
return rc;
}
Result SetSysMitmService::GetSettingsItemValue(Out<u64> out_size, OutBuffer<u8> out_value, InPointer<char> in_name, InPointer<char> in_key) {
char name[SET_MAX_NAME_SIZE] = {0};
char key[SET_MAX_NAME_SIZE] = {0};
Result rc = SettingsItemManager::ValidateName(in_name.pointer);
if (R_FAILED(rc)) {
return rc;
}
rc = SettingsItemManager::ValidateKey(in_key.pointer);
if (R_FAILED(rc)) {
return rc;
}
if (out_value.buffer == nullptr) {
return 0x19A69;
}
if (in_name.num_elements < SET_MAX_NAME_SIZE) {
strncpy(name, in_name.pointer, in_name.num_elements);
} else {
strncpy(name, in_name.pointer, SET_MAX_NAME_SIZE-1);
}
if (in_key.num_elements < SET_MAX_NAME_SIZE) {
strncpy(key, in_key.pointer, in_key.num_elements);
} else {
strncpy(key, in_key.pointer, SET_MAX_NAME_SIZE-1);
}
rc = SettingsItemManager::GetValue(name, key, out_value.buffer, out_value.num_elements, out_size.GetPointer());
if (R_FAILED(rc)) {
rc = setsysGetSettingsItemValueFwd(this->forward_service.get(), name, key, out_value.buffer, out_value.num_elements, out_size.GetPointer());
}
return rc;
}
Result SetSysMitmService::GetEdid(OutPointerWithServerSize<SetSysEdid, 0x1> out) {
return setsysGetEdidFwd(this->forward_service.get(), out.pointer);
}

View File

@@ -0,0 +1,65 @@
/*
* Copyright (c) 2018 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 "setsys_shim.h"
enum SetSysCmd : u32 {
SetSysCmd_GetFirmwareVersion = 3,
SetSysCmd_GetFirmwareVersion2 = 4,
SetSysCmd_GetSettingsItemValueSize = 37,
SetSysCmd_GetSettingsItemValue = 38,
/* Commands for which set:sys *must* act as a passthrough. */
/* TODO: Solve the relevant IPC detection problem. */
SetSysCmd_GetEdid = 41,
};
class SetSysMitmService : public IMitmServiceObject {
public:
SetSysMitmService(std::shared_ptr<Service> s, u64 pid) : IMitmServiceObject(s, pid) {
/* ... */
}
static bool ShouldMitm(u64 pid, u64 tid) {
/* Mitm everything. */
return true;
}
static void PostProcess(IMitmServiceObject *obj, IpcResponseContext *ctx);
protected:
/* Overridden commands. */
Result GetFirmwareVersion(OutPointerWithServerSize<SetSysFirmwareVersion, 0x1> out);
Result GetFirmwareVersion2(OutPointerWithServerSize<SetSysFirmwareVersion, 0x1> out);
Result GetSettingsItemValueSize(Out<u64> out_size, InPointer<char> name, InPointer<char> key);
Result GetSettingsItemValue(Out<u64> out_size, OutBuffer<u8> out_value, InPointer<char> name, InPointer<char> key);
/* Forced passthrough commands. */
Result GetEdid(OutPointerWithServerSize<SetSysEdid, 0x1> out);
public:
DEFINE_SERVICE_DISPATCH_TABLE {
MakeServiceCommandMeta<SetSysCmd_GetFirmwareVersion, &SetSysMitmService::GetFirmwareVersion>(),
MakeServiceCommandMeta<SetSysCmd_GetFirmwareVersion2, &SetSysMitmService::GetFirmwareVersion2>(),
MakeServiceCommandMeta<SetSysCmd_GetSettingsItemValueSize, &SetSysMitmService::GetSettingsItemValueSize>(),
MakeServiceCommandMeta<SetSysCmd_GetSettingsItemValue, &SetSysMitmService::GetSettingsItemValue>(),
MakeServiceCommandMeta<SetSysCmd_GetEdid, &SetSysMitmService::GetEdid>(),
};
};

View File

@@ -0,0 +1,309 @@
/*
* Copyright (c) 2018 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/>.
*/
#include <mutex>
#include <algorithm>
#include <map>
#include <string>
#include <utility>
#include <switch.h>
#include <strings.h>
#include <ctype.h>
#include "setsys_settings_items.hpp"
#include "../utils.hpp"
#include "../ini.h"
struct SettingsItemValue {
size_t size;
u8 *data;
};
std::map<std::string, SettingsItemValue> g_settings_items;
static bool g_threw_fatal = false;
static HosThread g_fatal_thread;
static void FatalThreadFunc(void *arg) {
Result rc = (Result)((uintptr_t)arg);
svcSleepThread(5000000000ULL);
fatalSimple(rc);
}
static bool IsCorrectFormat(const char *str, size_t len) {
if (len > 0 && str[len - 1] == '.') {
return false;
}
for (size_t i = 0; i < len; i++) {
const char c = *(str++);
if ('a' <= c && c <= 'z') {
continue;
}
if ('0' <= c && c <= '9') {
continue;
}
if (c == '-' || c == '.' || c == '_') {
continue;
}
return false;
}
return true;
}
Result SettingsItemManager::ValidateName(const char *name, size_t max_size) {
if (name == nullptr) {
return 0x19269;
}
const size_t name_len = strnlen(name, std::min(max_size, MaxNameLength + 1));
if (name_len == 0) {
return 0x1BA69;
} else if (name_len > MaxNameLength) {
return 0x1E269;
}
if (!IsCorrectFormat(name, name_len)) {
return 0x20A69;
}
return 0x0;
}
Result SettingsItemManager::ValidateName(const char *name) {
return ValidateName(name, MaxNameLength + 1);
}
Result SettingsItemManager::ValidateKey(const char *key, size_t max_size) {
if (key == nullptr) {
return 0x19469;
}
const size_t key_len = strnlen(key, std::min(max_size, MaxKeyLength + 1));
if (key_len == 0) {
return 0x1BC69;
} else if (key_len > MaxKeyLength) {
return 0x1E469;
}
if (!IsCorrectFormat(key, key_len)) {
return 0x20C69;
}
return 0x0;
}
Result SettingsItemManager::ValidateKey(const char *key) {
return ValidateKey(key, MaxKeyLength + 1);
}
static bool IsHexadecimal(const char *str) {
while (*str) {
if (isxdigit(*str)) {
str++;
} else {
return false;
}
}
return true;
}
static char hextoi(char c) {
if ('a' <= c && c <= 'f') return c - 'a' + 0xA;
if ('A' <= c && c <= 'F') return c - 'A' + 0xA;
if ('0' <= c && c <= '9') return c - '0';
return 0;
}
static Result ParseValue(const char *name, const char *key, const char *val_tup) {
const char *delimiter = strchr(val_tup, '!');
const char *value_str = delimiter + 1;
const char *type = val_tup;
if (delimiter == NULL) {
return 0x20E69;
}
while (isspace(*type) && type != delimiter) {
type++;
}
size_t type_len = delimiter - type;
size_t value_len = strlen(value_str);
if (delimiter == NULL || value_len == 0 || type_len == 0) {
return 0x20E69;
}
std::string kv = std::string(name) + "!" + std::string(key);
SettingsItemValue value;
if (strncasecmp(type, "str", type_len) == 0 || strncasecmp(type, "string", type_len) == 0) {
/* String */
value.size = value_len + 1;
value.data = reinterpret_cast<u8 *>(strdup(value_str));
if (value.data == nullptr) {
return 0xCC69;
}
} else if (strncasecmp(type, "hex", type_len) == 0 || strncasecmp(type, "bytes", type_len) == 0) {
/* hex */
if (value_len % 2 || !IsHexadecimal(value_str)) {
return 0x20E69;
}
value.size = value_len / 2;
u8 *data = reinterpret_cast<u8 *>(malloc(value.size));
if (data == nullptr) {
return 0xCC69;
}
memset(data, 0, value.size);
for (size_t i = 0; i < value_len; i++) {
data[i >> 1] |= hextoi(value_str[i]) << (4 * (i & 1));
}
value.data = data;
} else if (strncasecmp(type, "u8", type_len) == 0) {
/* u8 */
value.size = sizeof(u8);
u8 *data = reinterpret_cast<u8 *>(malloc(value.size));
if (data == nullptr) {
return 0xCC69;
}
*data = (u8)(strtoul(value_str, nullptr, 0));
value.data = reinterpret_cast<u8 *>(data);
} else if (strncasecmp(type, "u16", type_len) == 0) {
/* u16 */
value.size = sizeof(u16);
u16 *data = reinterpret_cast<u16 *>(malloc(value.size));
if (data == nullptr) {
return 0xCC69;
}
*data = (u16)(strtoul(value_str, nullptr, 0));
value.data = reinterpret_cast<u8 *>(data);
} else if (strncasecmp(type, "u32", type_len) == 0) {
/* u32 */
value.size = sizeof(u32);
u32 *data = reinterpret_cast<u32 *>(malloc(value.size));
if (data == nullptr) {
return 0xCC69;
}
*data = (u32)(strtoul(value_str, nullptr, 0));
value.data = reinterpret_cast<u8 *>(data);
} else if (strncasecmp(type, "u64", type_len) == 0) {
/* u64 */
value.size = sizeof(u64);
u64 *data = reinterpret_cast<u64 *>(malloc(value.size));
if (data == nullptr) {
return 0xCC69;
}
*data = (u64)(strtoul(value_str, nullptr, 0));
value.data = reinterpret_cast<u8 *>(data);
} else {
return 0x20E69;
}
g_settings_items[kv] = value;
return 0x0;
}
static int SettingsItemIniHandler(void *user, const char *name, const char *key, const char *value) {
Result rc = *(reinterpret_cast<Result *>(user));
ON_SCOPE_EXIT { *(reinterpret_cast<Result *>(user)) = rc; };
if (R_SUCCEEDED(rc)) {
rc = SettingsItemManager::ValidateName(name);
}
if (R_SUCCEEDED(rc)) {
rc = SettingsItemManager::ValidateKey(name);
}
if (R_SUCCEEDED(rc)) {
rc = ParseValue(name, key, value);
}
return R_SUCCEEDED(rc) ? 1 : 0;
}
void SettingsItemManager::LoadConfiguration() {
/* Open file. */
FsFile config_file;
Result rc = Utils::OpenSdFile("/atmosphere/system_settings.ini", FS_OPEN_READ, &config_file);
if (R_FAILED(rc)) {
return;
}
ON_SCOPE_EXIT {
fsFileClose(&config_file);
};
/* Allocate buffer. */
char *config_buf = new char[0x10000];
std::memset(config_buf, 0, 0x10000);
ON_SCOPE_EXIT {
delete config_buf;
};
/* Read from file. */
if (R_SUCCEEDED(rc)) {
size_t actual_size;
rc = fsFileRead(&config_file, 0, config_buf, 0xFFFF, &actual_size);
}
if (R_SUCCEEDED(rc)) {
ini_parse_string(config_buf, SettingsItemIniHandler, &rc);
}
/* Report error if we encountered one. */
if (R_FAILED(rc) && !g_threw_fatal) {
g_threw_fatal = true;
g_fatal_thread.Initialize(&FatalThreadFunc, reinterpret_cast<void *>(rc), 0x1000, 49);
g_fatal_thread.Start();
}
}
Result SettingsItemManager::GetValueSize(const char *name, const char *key, u64 *out_size) {
std::string kv = std::string(name) + "!" + std::string(key);
auto it = g_settings_items.find(kv);
if (it == g_settings_items.end()) {
return 0x1669;
}
*out_size = it->second.size;
return 0x0;
}
Result SettingsItemManager::GetValue(const char *name, const char *key, void *out, size_t max_size, u64 *out_size) {
std::string kv = std::string(name) + "!" + std::string(key);
auto it = g_settings_items.find(kv);
if (it == g_settings_items.end()) {
return 0x1669;
}
size_t copy_size = it->second.size;
if (max_size < copy_size) {
copy_size = max_size;
}
*out_size = copy_size;
memcpy(out, it->second.data, copy_size);
return 0x0;
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright (c) 2018 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>
class SettingsItemManager {
public:
static constexpr size_t MaxNameLength = 64;
static constexpr size_t MaxKeyLength = 64;
public:
static Result ValidateName(const char *name, size_t max_size);
static Result ValidateName(const char *name);
static Result ValidateKey(const char *key, size_t max_size);
static Result ValidateKey(const char *key);
static void LoadConfiguration();
static Result GetValueSize(const char *name, const char *key, u64 *out_size);
static Result GetValue(const char *name, const char *key, void *out, size_t max_size, u64 *out_size);
};

View File

@@ -0,0 +1,100 @@
/*
* Copyright (c) 2018 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/>.
*/
#include <string.h>
#include <switch.h>
#include "setsys_shim.h"
/* Command forwarders. */
Result setsysGetEdidFwd(Service* s, SetSysEdid* out) {
IpcCommand c;
ipcInitialize(&c);
ipcAddRecvStatic(&c, out, sizeof(*out), 0);
struct {
u64 magic;
u64 cmd_id;
} *raw;
raw = serviceIpcPrepareHeader(s, &c, sizeof(*raw));
raw->magic = SFCI_MAGIC;
raw->cmd_id = 41;
Result rc = serviceIpcDispatch(s);
if (R_SUCCEEDED(rc)) {
IpcParsedCommand r;
struct {
u64 magic;
u64 result;
} *resp;
serviceIpcParse(s, &r, sizeof(*resp));
resp = r.Raw;
rc = resp->result;
}
return rc;
}
Result setsysGetSettingsItemValueFwd(Service *s, const char *name, const char *item_key, void *value_out, size_t value_out_size, u64 *size_out) {
char send_name[SET_MAX_NAME_SIZE];
char send_item_key[SET_MAX_NAME_SIZE];
memset(send_name, 0, SET_MAX_NAME_SIZE);
memset(send_item_key, 0, SET_MAX_NAME_SIZE);
strncpy(send_name, name, SET_MAX_NAME_SIZE-1);
strncpy(send_item_key, item_key, SET_MAX_NAME_SIZE-1);
IpcCommand c;
ipcInitialize(&c);
ipcAddSendStatic(&c, send_name, SET_MAX_NAME_SIZE, 0);
ipcAddSendStatic(&c, send_item_key, SET_MAX_NAME_SIZE, 0);
ipcAddRecvBuffer(&c, value_out, value_out_size, 0);
struct {
u64 magic;
u64 cmd_id;
} *raw;
raw = serviceIpcPrepareHeader(s, &c, sizeof(*raw));
raw->magic = SFCI_MAGIC;
raw->cmd_id = 38;
Result rc = serviceIpcDispatch(s);
if (R_SUCCEEDED(rc)) {
IpcParsedCommand r;
struct {
u64 magic;
u64 result;
u64 size_out;
} *resp;
serviceIpcParse(s, &r, sizeof(*resp));
resp = r.Raw;
rc = resp->result;
if (R_SUCCEEDED(rc)) {
*size_out = resp->size_out;
}
}
return rc;
}

View File

@@ -0,0 +1,24 @@
/**
* @file setsys_shim.h
* @brief System Settings Services (set:sys) IPC wrapper. To be merged into libnx, eventually.
* @author SciresM
* @copyright libnx Authors
*/
#pragma once
#include <switch.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef struct {
char edid[0x100];
} SetSysEdid;
/* Command forwarders. */
Result setsysGetEdidFwd(Service* s, SetSysEdid* out);
Result setsysGetSettingsItemValueFwd(Service* s, const char *name, const char *item_key, void *value_out, size_t value_out_size, u64 *size_out);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,113 @@
/* Based on linux source code */
/*
* sha256_base.h - core logic for SHA-256 implementations
*
* Copyright (C) 2015 Linaro Ltd <ard.biesheuvel@linaro.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#ifdef __cplusplus
extern "C" {
#endif
#include <string.h>
#include "sha256.h"
#define unlikely(x) __builtin_expect(!!(x), 0)
void sha256_block_data_order (uint32_t *ctx, const void *in, size_t num);
int sha256_init(struct sha256_state *sctx)
{
sctx->state[0] = SHA256_H0;
sctx->state[1] = SHA256_H1;
sctx->state[2] = SHA256_H2;
sctx->state[3] = SHA256_H3;
sctx->state[4] = SHA256_H4;
sctx->state[5] = SHA256_H5;
sctx->state[6] = SHA256_H6;
sctx->state[7] = SHA256_H7;
sctx->count = 0;
return 0;
}
int sha256_update(struct sha256_state *sctx,
const void *data,
size_t len)
{
const u8 *data8 = (const u8 *)data;
unsigned int len32 = (unsigned int)len;
unsigned int partial = sctx->count % SHA256_BLOCK_SIZE;
sctx->count += len32;
if (unlikely((partial + len32) >= SHA256_BLOCK_SIZE)) {
int blocks;
if (partial) {
int p = SHA256_BLOCK_SIZE - partial;
memcpy(sctx->buf + partial, data8, p);
data8 += p;
len32 -= p;
sha256_block_data_order(sctx->state, sctx->buf, 1);
}
blocks = len32 / SHA256_BLOCK_SIZE;
len32 %= SHA256_BLOCK_SIZE;
if (blocks) {
sha256_block_data_order(sctx->state, data8, blocks);
data8 += blocks * SHA256_BLOCK_SIZE;
}
partial = 0;
}
if (len32)
memcpy(sctx->buf + partial, data8, len32);
return 0;
}
int sha256_finalize(struct sha256_state *sctx)
{
const int bit_offset = SHA256_BLOCK_SIZE - sizeof(u64);
u64 *bits = (u64 *)(sctx->buf + bit_offset);
unsigned int partial = sctx->count % SHA256_BLOCK_SIZE;
sctx->buf[partial++] = 0x80;
if (partial > bit_offset) {
memset(sctx->buf + partial, 0x0, SHA256_BLOCK_SIZE - partial);
partial = 0;
sha256_block_data_order(sctx->state, sctx->buf, 1);
}
memset(sctx->buf + partial, 0x0, bit_offset - partial);
*bits = __builtin_bswap64(sctx->count << 3);
sha256_block_data_order(sctx->state, sctx->buf, 1);
return 0;
}
int sha256_finish(struct sha256_state *sctx, void *out)
{
unsigned int digest_size = 32;
u32 *digest = (u32 *)out;
int i;
// Switch: misalignment shouldn't be a problem here...
for (i = 0; digest_size > 0; i++, digest_size -= sizeof(u32))
*digest++ = __builtin_bswap32(sctx->state[i]);
*sctx = (struct sha256_state){};
return 0;
}
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,36 @@
#pragma once
/* Based on linux source code */
#ifdef __cplusplus
extern "C" {
#endif
#include <switch/types.h>
#define SHA256_DIGEST_SIZE 32
#define SHA256_BLOCK_SIZE 64
#define SHA256_H0 0x6a09e667UL
#define SHA256_H1 0xbb67ae85UL
#define SHA256_H2 0x3c6ef372UL
#define SHA256_H3 0xa54ff53aUL
#define SHA256_H4 0x510e527fUL
#define SHA256_H5 0x9b05688cUL
#define SHA256_H6 0x1f83d9abUL
#define SHA256_H7 0x5be0cd19UL
struct sha256_state {
u32 state[SHA256_DIGEST_SIZE / 4];
u64 count;
u8 buf[SHA256_BLOCK_SIZE];
};
int sha256_init(struct sha256_state *sctx);
int sha256_update(struct sha256_state *sctx, const void *data, size_t len);
int sha256_finalize(struct sha256_state *sctx);
int sha256_finish(struct sha256_state *sctx, void *out);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,163 @@
.section .text.sha256_armv8, "ax", %progbits
.align 5
.arch armv8-a+crypto
# SHA256 assembly implementation for ARMv8 AArch64 (based on linux source code)
.global sha256_block_data_order
.type sha256_block_data_order,%function
sha256_block_data_order:
.Lsha256prolog:
stp x29, x30, [sp,#-64]!
mov x29, sp
adr x3, .LKConstant256
str q8, [sp, #16]
ld1 {v16.4s-v19.4s}, [x3], #64
ld1 {v0.4s}, [x0], #16
ld1 {v20.4s-v23.4s}, [x3], #64
add x2, x1, x2, lsl #6
ld1 {v1.4s}, [x0]
ld1 {v24.4s-v27.4s}, [x3], #64
sub x0, x0, #16
str q9, [sp, #32]
str q10, [sp, #48]
ld1 {v28.4s-v31.4s}, [x3], #64
.Lsha256loop:
ld1 {v5.16b-v8.16b}, [x1], #64
mov v2.16b, v0.16b
mov v3.16b, v1.16b
rev32 v5.16b, v5.16b
rev32 v6.16b, v6.16b
add v9.4s, v5.4s, v16.4s
rev32 v7.16b, v7.16b
add v10.4s, v6.4s, v17.4s
mov v4.16b, v2.16b
sha256h q2, q3, v9.4s
sha256h2 q3, q4, v9.4s
sha256su0 v5.4s, v6.4s
rev32 v8.16b, v8.16b
add v9.4s, v7.4s, v18.4s
mov v4.16b, v2.16b
sha256h q2, q3, v10.4s
sha256h2 q3, q4, v10.4s
sha256su0 v6.4s, v7.4s
sha256su1 v5.4s, v7.4s, v8.4s
add v10.4s, v8.4s, v19.4s
mov v4.16b, v2.16b
sha256h q2, q3, v9.4s
sha256h2 q3, q4, v9.4s
sha256su0 v7.4s, v8.4s
sha256su1 v6.4s, v8.4s, v5.4s
add v9.4s, v5.4s, v20.4s
mov v4.16b, v2.16b
sha256h q2, q3, v10.4s
sha256h2 q3, q4, v10.4s
sha256su0 v8.4s, v5.4s
sha256su1 v7.4s, v5.4s, v6.4s
add v10.4s, v6.4s, v21.4s
mov v4.16b, v2.16b
sha256h q2, q3, v9.4s
sha256h2 q3, q4, v9.4s
sha256su0 v5.4s, v6.4s
sha256su1 v8.4s, v6.4s, v7.4s
add v9.4s, v7.4s, v22.4s
mov v4.16b, v2.16b
sha256h q2, q3, v10.4s
sha256h2 q3, q4, v10.4s
sha256su0 v6.4s, v7.4s
sha256su1 v5.4s, v7.4s, v8.4s
add v10.4s, v8.4s, v23.4s
mov v4.16b, v2.16b
sha256h q2, q3, v9.4s
sha256h2 q3, q4, v9.4s
sha256su0 v7.4s, v8.4s
sha256su1 v6.4s, v8.4s, v5.4s
add v9.4s, v5.4s, v24.4s
mov v4.16b, v2.16b
sha256h q2, q3, v10.4s
sha256h2 q3, q4, v10.4s
sha256su0 v8.4s, v5.4s
sha256su1 v7.4s, v5.4s, v6.4s
add v10.4s, v6.4s, v25.4s
mov v4.16b, v2.16b
sha256h q2, q3, v9.4s
sha256h2 q3, q4, v9.4s
sha256su0 v5.4s, v6.4s
sha256su1 v8.4s, v6.4s, v7.4s
add v9.4s, v7.4s, v26.4s
mov v4.16b, v2.16b
sha256h q2, q3, v10.4s
sha256h2 q3, q4, v10.4s
sha256su0 v6.4s, v7.4s
sha256su1 v5.4s, v7.4s, v8.4s
add v10.4s, v8.4s, v27.4s
mov v4.16b, v2.16b
sha256h q2, q3, v9.4s
sha256h2 q3, q4, v9.4s
sha256su0 v7.4s, v8.4s
sha256su1 v6.4s, v8.4s, v5.4s
add v9.4s, v5.4s, v28.4s
mov v4.16b, v2.16b
sha256h q2, q3, v10.4s
sha256h2 q3, q4, v10.4s
sha256su0 v8.4s, v5.4s
sha256su1 v7.4s, v5.4s, v6.4s
add v10.4s, v6.4s, v29.4s
mov v4.16b, v2.16b
sha256h q2, q3, v9.4s
sha256h2 q3, q4, v9.4s
sha256su1 v8.4s, v6.4s, v7.4s
add v9.4s, v7.4s, v30.4s
mov v4.16b, v2.16b
sha256h q2, q3, v10.4s
sha256h2 q3, q4, v10.4s
add v10.4s, v8.4s, v31.4s
mov v4.16b, v2.16b
sha256h q2, q3, v9.4s
sha256h2 q3, q4, v9.4s
mov v4.16b, v2.16b
sha256h q2, q3, v10.4s
sha256h2 q3, q4, v10.4s
cmp x1, x2
add v1.4s, v1.4s, v3.4s
add v0.4s, v0.4s, v2.4s
b.ne .Lsha256loop
.Lsha256epilog:
st1 {v0.4s,v1.4s}, [x0]
ldr q10, [sp, #48]
ldr q9, [sp, #32]
ldr q8, [sp, #16]
ldr x29, [sp], #64
ret
.align 5
.LKConstant256:
.word 0x428a2f98,0x71374491,0xb5c0fbcf,0xe9b5dba5
.word 0x3956c25b,0x59f111f1,0x923f82a4,0xab1c5ed5
.word 0xd807aa98,0x12835b01,0x243185be,0x550c7dc3
.word 0x72be5d74,0x80deb1fe,0x9bdc06a7,0xc19bf174
.word 0xe49b69c1,0xefbe4786,0x0fc19dc6,0x240ca1cc
.word 0x2de92c6f,0x4a7484aa,0x5cb0a9dc,0x76f988da
.word 0x983e5152,0xa831c66d,0xb00327c8,0xbf597fc7
.word 0xc6e00bf3,0xd5a79147,0x06ca6351,0x14292967
.word 0x27b70a85,0x2e1b2138,0x4d2c6dfc,0x53380d13
.word 0x650a7354,0x766a0abb,0x81c2c92e,0x92722c85
.word 0xa2bfe8a1,0xa81a664b,0xc24b8b70,0xc76c51a3
.word 0xd192e819,0xd6990624,0xf40e3585,0x106aa070
.word 0x19a4c116,0x1e376c08,0x2748774c,0x34b0bcb5
.word 0x391c0cb3,0x4ed8aa4a,0x5b9cca4f,0x682e6ff3
.word 0x748f82ee,0x78a5636f,0x84c87814,0x8cc70208
.word 0x90befffa,0xa4506ceb,0xbef9a3f7,0xc67178f2
.size sha256_block_data_order,.-sha256_block_data_order
.align 2

View File

@@ -0,0 +1,544 @@
/*
* Copyright (c) 2018 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/>.
*/
#include <switch.h>
#include <stratosphere.hpp>
#include <atomic>
#include <algorithm>
#include <strings.h>
#include "debug.hpp"
#include "utils.hpp"
#include "ini.h"
#include "sha256.h"
static FsFileSystem g_sd_filesystem = {0};
static HosSignal g_sd_signal;
static std::vector<u64> g_mitm_flagged_tids;
static std::vector<u64> g_disable_mitm_flagged_tids;
static std::atomic_bool g_has_initialized = false;
static std::atomic_bool g_has_hid_session = false;
static u64 g_override_key_combination = KEY_R;
static u64 g_override_hbl_tid = 0x010000000000100DULL;
static bool g_override_any_app = false;
static bool g_override_by_default = true;
/* Static buffer for loader.ini contents at runtime. */
static char g_config_ini_data[0x800];
/* Backup file for CAL0 partition. */
static constexpr size_t ProdinfoSize = 0x8000;
static FsFile g_cal0_file = {0};
static u8 g_cal0_storage_backup[ProdinfoSize];
static u8 g_cal0_backup[ProdinfoSize];
static bool IsHexadecimal(const char *str) {
while (*str) {
if (isxdigit(*str)) {
str++;
} else {
return false;
}
}
return true;
}
void Utils::InitializeThreadFunc(void *args) {
/* Get required services. */
Handle tmp_hnd = 0;
static const char * const required_active_services[] = {"pcv", "gpio", "pinmux", "psc:c"};
for (unsigned int i = 0; i < sizeof(required_active_services) / sizeof(required_active_services[0]); i++) {
if (R_FAILED(smGetServiceOriginal(&tmp_hnd, smEncodeName(required_active_services[i])))) {
/* TODO: Panic */
} else {
svcCloseHandle(tmp_hnd);
}
}
/* Mount SD. */
while (R_FAILED(fsMountSdcard(&g_sd_filesystem))) {
svcSleepThread(1000000ULL);
}
/* Back up CAL0, if it's not backed up already. */
fsFsCreateDirectory(&g_sd_filesystem, "/atmosphere/automatic_backups");
{
FsStorage cal0_storage;
if (R_FAILED(fsOpenBisStorage(&cal0_storage, BisStorageId_Prodinfo)) || R_FAILED(fsStorageRead(&cal0_storage, 0, g_cal0_storage_backup, ProdinfoSize))) {
std::abort();
}
fsStorageClose(&cal0_storage);
char serial_number[0x40] = {0};
memcpy(serial_number, g_cal0_storage_backup + 0x250, 0x18);
char prodinfo_backup_path[FS_MAX_PATH] = {0};
if (strlen(serial_number) > 0) {
snprintf(prodinfo_backup_path, sizeof(prodinfo_backup_path) - 1, "/atmosphere/automatic_backups/%s_PRODINFO.bin", serial_number);
} else {
snprintf(prodinfo_backup_path, sizeof(prodinfo_backup_path) - 1, "/atmosphere/automatic_backups/PRODINFO.bin");
}
fsFsCreateFile(&g_sd_filesystem, prodinfo_backup_path, ProdinfoSize, 0);
if (R_SUCCEEDED(fsFsOpenFile(&g_sd_filesystem, prodinfo_backup_path, FS_OPEN_READ | FS_OPEN_WRITE, &g_cal0_file))) {
bool has_auto_backup = false;
size_t read = 0;
if (R_SUCCEEDED(fsFileRead(&g_cal0_file, 0, g_cal0_backup, sizeof(g_cal0_backup), &read)) && read == sizeof(g_cal0_backup)) {
bool is_cal0_valid = true;
is_cal0_valid &= memcmp(g_cal0_backup, "CAL0", 4) == 0;
is_cal0_valid &= memcmp(g_cal0_backup + 0x250, serial_number, 0x18) == 0;
u32 cal0_size = ((u32 *)g_cal0_backup)[2];
is_cal0_valid &= cal0_size + 0x40 <= ProdinfoSize;
if (is_cal0_valid) {
struct sha256_state sha_ctx;
u8 calc_hash[0x20];
sha256_init(&sha_ctx);
sha256_update(&sha_ctx, g_cal0_backup + 0x40, cal0_size);
sha256_finalize(&sha_ctx);
sha256_finish(&sha_ctx, calc_hash);
is_cal0_valid &= memcmp(calc_hash, g_cal0_backup + 0x20, sizeof(calc_hash)) == 0;
}
has_auto_backup = is_cal0_valid;
}
if (!has_auto_backup) {
fsFileSetSize(&g_cal0_file, ProdinfoSize);
fsFileWrite(&g_cal0_file, 0, g_cal0_backup, ProdinfoSize);
fsFileFlush(&g_cal0_file);
}
/* NOTE: g_cal0_file is intentionally not closed here. This prevents any other process from opening it. */
memset(g_cal0_storage_backup, 0, sizeof(g_cal0_storage_backup));
memset(g_cal0_backup, 0, sizeof(g_cal0_backup));
}
}
/* Check for MitM flags. */
FsDir titles_dir;
if (R_SUCCEEDED(fsFsOpenDirectory(&g_sd_filesystem, "/atmosphere/titles", FS_DIROPEN_DIRECTORY, &titles_dir))) {
FsDirectoryEntry dir_entry;
FsFile f;
u64 read_entries;
while (R_SUCCEEDED((fsDirRead(&titles_dir, 0, &read_entries, 1, &dir_entry))) && read_entries == 1) {
if (strlen(dir_entry.name) == 0x10 && IsHexadecimal(dir_entry.name)) {
u64 title_id = strtoul(dir_entry.name, NULL, 16);
char title_path[FS_MAX_PATH] = {0};
strcpy(title_path, "/atmosphere/titles/");
strcat(title_path, dir_entry.name);
strcat(title_path, "/flags/fsmitm.flag");
if (R_SUCCEEDED(fsFsOpenFile(&g_sd_filesystem, title_path, FS_OPEN_READ, &f))) {
g_mitm_flagged_tids.push_back(title_id);
fsFileClose(&f);
} else {
/* TODO: Deprecate. */
memset(title_path, 0, sizeof(title_path));
strcpy(title_path, "/atmosphere/titles/");
strcat(title_path, dir_entry.name);
strcat(title_path, "/fsmitm.flag");
if (R_SUCCEEDED(fsFsOpenFile(&g_sd_filesystem, title_path, FS_OPEN_READ, &f))) {
g_mitm_flagged_tids.push_back(title_id);
fsFileClose(&f);
}
}
memset(title_path, 0, sizeof(title_path));
strcpy(title_path, "/atmosphere/titles/");
strcat(title_path, dir_entry.name);
strcat(title_path, "/flags/fsmitm_disable.flag");
if (R_SUCCEEDED(fsFsOpenFile(&g_sd_filesystem, title_path, FS_OPEN_READ, &f))) {
g_disable_mitm_flagged_tids.push_back(title_id);
fsFileClose(&f);
} else {
/* TODO: Deprecate. */
memset(title_path, 0, sizeof(title_path));
strcpy(title_path, "/atmosphere/titles/");
strcat(title_path, dir_entry.name);
strcat(title_path, "/fsmitm_disable.flag");
if (R_SUCCEEDED(fsFsOpenFile(&g_sd_filesystem, title_path, FS_OPEN_READ, &f))) {
g_disable_mitm_flagged_tids.push_back(title_id);
fsFileClose(&f);
}
}
}
}
fsDirClose(&titles_dir);
}
Utils::RefreshConfiguration();
/* Initialize set:sys. */
setsysInitialize();
/* Signal SD is initialized. */
g_has_initialized = true;
g_sd_signal.Signal();
/* Initialize HID. */
{
while (R_FAILED(hidInitialize())) {
svcSleepThread(1000000ULL);
}
g_has_hid_session = true;
hidExit();
}
svcExitThread();
}
bool Utils::IsSdInitialized() {
return g_has_initialized;
}
void Utils::WaitSdInitialized() {
g_sd_signal.Wait();
}
bool Utils::IsHidAvailable() {
return g_has_hid_session;
}
Result Utils::OpenSdFile(const char *fn, int flags, FsFile *out) {
if (!IsSdInitialized()) {
return 0xFA202;
}
return fsFsOpenFile(&g_sd_filesystem, fn, flags, out);
}
Result Utils::OpenSdFileForAtmosphere(u64 title_id, const char *fn, int flags, FsFile *out) {
if (!IsSdInitialized()) {
return 0xFA202;
}
char path[FS_MAX_PATH];
if (*fn == '/') {
snprintf(path, sizeof(path), "/atmosphere/titles/%016lx%s", title_id, fn);
} else {
snprintf(path, sizeof(path), "/atmosphere/titles/%016lx/%s", title_id, fn);
}
return fsFsOpenFile(&g_sd_filesystem, path, flags, out);
}
Result Utils::OpenRomFSSdFile(u64 title_id, const char *fn, int flags, FsFile *out) {
if (!IsSdInitialized()) {
return 0xFA202;
}
return OpenRomFSFile(&g_sd_filesystem, title_id, fn, flags, out);
}
Result Utils::OpenSdDir(const char *path, FsDir *out) {
if (!IsSdInitialized()) {
return 0xFA202;
}
return fsFsOpenDirectory(&g_sd_filesystem, path, FS_DIROPEN_DIRECTORY | FS_DIROPEN_FILE, out);
}
Result Utils::OpenSdDirForAtmosphere(u64 title_id, const char *path, FsDir *out) {
if (!IsSdInitialized()) {
return 0xFA202;
}
char safe_path[FS_MAX_PATH];
if (*path == '/') {
snprintf(safe_path, sizeof(safe_path), "/atmosphere/titles/%016lx%s", title_id, path);
} else {
snprintf(safe_path, sizeof(safe_path), "/atmosphere/titles/%016lx/%s", title_id, path);
}
return fsFsOpenDirectory(&g_sd_filesystem, safe_path, FS_DIROPEN_DIRECTORY | FS_DIROPEN_FILE, out);
}
Result Utils::OpenRomFSSdDir(u64 title_id, const char *path, FsDir *out) {
if (!IsSdInitialized()) {
return 0xFA202;
}
return OpenRomFSDir(&g_sd_filesystem, title_id, path, out);
}
Result Utils::OpenRomFSFile(FsFileSystem *fs, u64 title_id, const char *fn, int flags, FsFile *out) {
char path[FS_MAX_PATH];
if (*fn == '/') {
snprintf(path, sizeof(path), "/atmosphere/titles/%016lx/romfs%s", title_id, fn);
} else {
snprintf(path, sizeof(path), "/atmosphere/titles/%016lx/romfs/%s", title_id, fn);
}
return fsFsOpenFile(fs, path, flags, out);
}
Result Utils::OpenRomFSDir(FsFileSystem *fs, u64 title_id, const char *path, FsDir *out) {
char safe_path[FS_MAX_PATH];
if (*path == '/') {
snprintf(safe_path, sizeof(safe_path), "/atmosphere/titles/%016lx/romfs%s", title_id, path);
} else {
snprintf(safe_path, sizeof(safe_path), "/atmosphere/titles/%016lx/romfs/%s", title_id, path);
}
return fsFsOpenDirectory(fs, safe_path, FS_DIROPEN_DIRECTORY | FS_DIROPEN_FILE, out);
}
bool Utils::HasSdRomfsContent(u64 title_id) {
/* Check for romfs.bin. */
FsFile data_file;
if (R_SUCCEEDED(Utils::OpenSdFileForAtmosphere(title_id, "romfs.bin", FS_OPEN_READ, &data_file))) {
fsFileClose(&data_file);
return true;
}
/* Check for romfs folder with non-zero content. */
FsDir dir;
if (R_FAILED(Utils::OpenRomFSSdDir(title_id, "", &dir))) {
return false;
}
ON_SCOPE_EXIT {
fsDirClose(&dir);
};
FsDirectoryEntry dir_entry;
u64 read_entries;
return R_SUCCEEDED(fsDirRead(&dir, 0, &read_entries, 1, &dir_entry)) && read_entries == 1;
}
Result Utils::SaveSdFileForAtmosphere(u64 title_id, const char *fn, void *data, size_t size) {
if (!IsSdInitialized()) {
return 0xFA202;
}
Result rc = 0;
char path[FS_MAX_PATH];
if (*fn == '/') {
snprintf(path, sizeof(path), "/atmosphere/titles/%016lx%s", title_id, fn);
} else {
snprintf(path, sizeof(path), "/atmosphere/titles/%016lx/%s", title_id, fn);
}
/* Unconditionally create. */
FsFile f;
fsFsCreateFile(&g_sd_filesystem, path, size, 0);
/* Try to open. */
rc = fsFsOpenFile(&g_sd_filesystem, path, FS_OPEN_READ | FS_OPEN_WRITE, &f);
if (R_FAILED(rc)) {
return rc;
}
/* Always close, if we opened. */
ON_SCOPE_EXIT {
fsFileClose(&f);
};
/* Try to make it big enough. */
rc = fsFileSetSize(&f, size);
if (R_FAILED(rc)) {
return rc;
}
/* Try to write the data. */
rc = fsFileWrite(&f, 0, data, size);
return rc;
}
bool Utils::IsHblTid(u64 tid) {
return (g_override_any_app && tid >= 0x0100000000010000ULL) || (!g_override_any_app && tid == g_override_hbl_tid);
}
bool Utils::HasTitleFlag(u64 tid, const char *flag) {
if (IsSdInitialized()) {
FsFile f;
char flag_path[FS_MAX_PATH];
memset(flag_path, 0, sizeof(flag_path));
snprintf(flag_path, sizeof(flag_path) - 1, "flags/%s.flag", flag);
if (OpenSdFileForAtmosphere(tid, flag_path, FS_OPEN_READ, &f)) {
fsFileClose(&f);
return true;
}
/* TODO: Deprecate. */
snprintf(flag_path, sizeof(flag_path) - 1, "%s.flag", flag);
if (OpenSdFileForAtmosphere(tid, flag_path, FS_OPEN_READ, &f)) {
fsFileClose(&f);
return true;
}
}
return false;
}
bool Utils::HasGlobalFlag(const char *flag) {
if (IsSdInitialized()) {
FsFile f;
char flag_path[FS_MAX_PATH] = {0};
snprintf(flag_path, sizeof(flag_path), "/atmosphere/flags/%s.flag", flag);
if (fsFsOpenFile(&g_sd_filesystem, flag_path, FS_OPEN_READ, &f)) {
fsFileClose(&f);
return true;
}
}
return false;
}
bool Utils::HasHblFlag(const char *flag) {
char hbl_flag[FS_MAX_PATH] = {0};
snprintf(hbl_flag, sizeof(hbl_flag), "hbl_%s", flag);
return HasGlobalFlag(hbl_flag);
}
bool Utils::HasFlag(u64 tid, const char *flag) {
return HasTitleFlag(tid, flag) || (IsHblTid(tid) && HasHblFlag(flag));
}
bool Utils::HasSdMitMFlag(u64 tid) {
if (IsHblTid(tid)) {
return true;
}
if (IsSdInitialized()) {
return std::find(g_mitm_flagged_tids.begin(), g_mitm_flagged_tids.end(), tid) != g_mitm_flagged_tids.end();
}
return false;
}
bool Utils::HasSdDisableMitMFlag(u64 tid) {
if (IsSdInitialized()) {
return std::find(g_disable_mitm_flagged_tids.begin(), g_disable_mitm_flagged_tids.end(), tid) != g_disable_mitm_flagged_tids.end();
}
return false;
}
Result Utils::GetKeysDown(u64 *keys) {
if (!Utils::IsHidAvailable() || R_FAILED(hidInitialize())) {
return MAKERESULT(Module_Libnx, LibnxError_InitFail_HID);
}
hidScanInput();
*keys = hidKeysDown(CONTROLLER_P1_AUTO);
hidExit();
return 0x0;
}
bool Utils::HasOverrideButton(u64 tid) {
if (tid < 0x0100000000010000ULL || !IsSdInitialized()) {
/* Disable button override disable for non-applications. */
return true;
}
/* Unconditionally refresh loader.ini contents. */
RefreshConfiguration();
u64 kDown = 0;
bool keys_triggered = (R_SUCCEEDED(GetKeysDown(&kDown)) && ((kDown & g_override_key_combination) != 0));
return IsSdInitialized() && (g_override_by_default ^ keys_triggered);
}
static int FsMitMIniHandler(void *user, const char *section, const char *name, const char *value) {
/* Taken and modified, with love, from Rajkosto's implementation. */
if (strcasecmp(section, "config") == 0) {
if (strcasecmp(name, "hbl_tid") == 0) {
if (strcasecmp(value, "app") == 0) {
g_override_any_app = true;
}
else {
u64 override_tid = strtoul(value, NULL, 16);
if (override_tid != 0) {
g_override_hbl_tid = override_tid;
}
}
} else if (strcasecmp(name, "override_key") == 0) {
if (value[0] == '!') {
g_override_by_default = true;
value++;
} else {
g_override_by_default = false;
}
if (strcasecmp(value, "A") == 0) {
g_override_key_combination = KEY_A;
} else if (strcasecmp(value, "B") == 0) {
g_override_key_combination = KEY_B;
} else if (strcasecmp(value, "X") == 0) {
g_override_key_combination = KEY_X;
} else if (strcasecmp(value, "Y") == 0) {
g_override_key_combination = KEY_Y;
} else if (strcasecmp(value, "LS") == 0) {
g_override_key_combination = KEY_LSTICK;
} else if (strcasecmp(value, "RS") == 0) {
g_override_key_combination = KEY_RSTICK;
} else if (strcasecmp(value, "L") == 0) {
g_override_key_combination = KEY_L;
} else if (strcasecmp(value, "R") == 0) {
g_override_key_combination = KEY_R;
} else if (strcasecmp(value, "ZL") == 0) {
g_override_key_combination = KEY_ZL;
} else if (strcasecmp(value, "ZR") == 0) {
g_override_key_combination = KEY_ZR;
} else if (strcasecmp(value, "PLUS") == 0) {
g_override_key_combination = KEY_PLUS;
} else if (strcasecmp(value, "MINUS") == 0) {
g_override_key_combination = KEY_MINUS;
} else if (strcasecmp(value, "DLEFT") == 0) {
g_override_key_combination = KEY_DLEFT;
} else if (strcasecmp(value, "DUP") == 0) {
g_override_key_combination = KEY_DUP;
} else if (strcasecmp(value, "DRIGHT") == 0) {
g_override_key_combination = KEY_DRIGHT;
} else if (strcasecmp(value, "DDOWN") == 0) {
g_override_key_combination = KEY_DDOWN;
} else if (strcasecmp(value, "SL") == 0) {
g_override_key_combination = KEY_SL;
} else if (strcasecmp(value, "SR") == 0) {
g_override_key_combination = KEY_SR;
} else {
g_override_key_combination = 0;
}
}
} else {
return 0;
}
return 1;
}
void Utils::RefreshConfiguration() {
FsFile config_file;
if (R_FAILED(fsFsOpenFile(&g_sd_filesystem, "/atmosphere/loader.ini", FS_OPEN_READ, &config_file))) {
return;
}
u64 size;
if (R_FAILED(fsFileGetSize(&config_file, &size))) {
return;
}
size = std::min(size, (decltype(size))0x7FF);
/* Read in string. */
std::fill(g_config_ini_data, g_config_ini_data + 0x800, 0);
size_t r_s;
fsFileRead(&config_file, 0, g_config_ini_data, size, &r_s);
fsFileClose(&config_file);
ini_parse_string(g_config_ini_data, FsMitMIniHandler, NULL);
}

View File

@@ -0,0 +1,78 @@
/*
* Copyright (c) 2018 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>
enum BisStorageId : u32 {
BisStorageId_Boot0 = 0,
BisStorageId_Boot1 = 10,
BisStorageId_RawNand = 20,
BisStorageId_BcPkg2_1 = 21,
BisStorageId_BcPkg2_2 = 22,
BisStorageId_BcPkg2_3 = 23,
BisStorageId_BcPkg2_4 = 24,
BisStorageId_BcPkg2_5 = 25,
BisStorageId_BcPkg2_6 = 26,
BisStorageId_Prodinfo = 27,
BisStorageId_ProdinfoF = 28,
BisStorageId_Safe = 29,
BisStorageId_User = 30,
BisStorageId_System = 31,
BisStorageId_SystemProperEncryption = 32,
BisStorageId_SystemProperPartition = 33,
};
class Utils {
public:
static bool IsSdInitialized();
static void WaitSdInitialized();
static Result OpenSdFile(const char *fn, int flags, FsFile *out);
static Result OpenSdFileForAtmosphere(u64 title_id, const char *fn, int flags, FsFile *out);
static Result OpenRomFSSdFile(u64 title_id, const char *fn, int flags, FsFile *out);
static Result OpenSdDir(const char *path, FsDir *out);
static Result OpenSdDirForAtmosphere(u64 title_id, const char *path, FsDir *out);
static Result OpenRomFSSdDir(u64 title_id, const char *path, FsDir *out);
static Result OpenRomFSFile(FsFileSystem *fs, u64 title_id, const char *fn, int flags, FsFile *out);
static Result OpenRomFSDir(FsFileSystem *fs, u64 title_id, const char *path, FsDir *out);
static Result SaveSdFileForAtmosphere(u64 title_id, const char *fn, void *data, size_t size);
static bool HasSdRomfsContent(u64 title_id);
/* Delayed Initialization + MitM detection. */
static void InitializeThreadFunc(void *args);
static bool IsHblTid(u64 tid);
static bool HasTitleFlag(u64 tid, const char *flag);
static bool HasHblFlag(const char *flag);
static bool HasGlobalFlag(const char *flag);
static bool HasFlag(u64 tid, const char *flag);
static bool HasSdMitMFlag(u64 tid);
static bool HasSdDisableMitMFlag(u64 tid);
static bool IsHidAvailable();
static Result GetKeysDown(u64 *keys);
static bool HasOverrideButton(u64 tid);
private:
static void RefreshConfiguration();
};