2019-06-26 23:46:19 +01:00
|
|
|
/*
|
|
|
|
* Copyright (c) 2018-2019 Atmosphère-NX
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
|
|
* under the terms and conditions of the GNU General Public License,
|
|
|
|
* version 2, as published by the Free Software Foundation.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
|
|
* more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
#include "ldr_capabilities.hpp"
|
|
|
|
#include "ldr_content_management.hpp"
|
|
|
|
#include "ldr_meta.hpp"
|
|
|
|
|
2019-10-24 10:30:10 +01:00
|
|
|
namespace ams::ldr {
|
2019-06-26 23:46:19 +01:00
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
/* Convenience definitions. */
|
|
|
|
constexpr size_t MetaCacheBufferSize = 0x8000;
|
|
|
|
constexpr const char *MetaFilePath = "/main.npdm";
|
|
|
|
|
|
|
|
/* Types. */
|
|
|
|
struct MetaCache {
|
|
|
|
Meta meta;
|
|
|
|
u8 buffer[MetaCacheBufferSize];
|
|
|
|
};
|
|
|
|
|
|
|
|
/* Global storage. */
|
2019-10-28 04:43:01 +00:00
|
|
|
ncm::ProgramId g_cached_program_id;
|
2019-11-21 12:03:19 +00:00
|
|
|
cfg::OverrideStatus g_cached_override_status;
|
2019-06-26 23:46:19 +01:00
|
|
|
MetaCache g_meta_cache;
|
|
|
|
MetaCache g_original_meta_cache;
|
|
|
|
|
|
|
|
/* Helpers. */
|
|
|
|
Result ValidateSubregion(size_t allowed_start, size_t allowed_end, size_t start, size_t size, size_t min_size = 0) {
|
2019-10-24 09:40:44 +01:00
|
|
|
R_UNLESS(size >= min_size, ResultInvalidMeta());
|
|
|
|
R_UNLESS(allowed_start <= start, ResultInvalidMeta());
|
|
|
|
R_UNLESS(start <= allowed_end, ResultInvalidMeta());
|
|
|
|
R_UNLESS(start + size <= allowed_end, ResultInvalidMeta());
|
|
|
|
return ResultSuccess();
|
2019-06-26 23:46:19 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
Result ValidateNpdm(const Npdm *npdm, size_t size) {
|
|
|
|
/* Validate magic. */
|
2019-10-24 09:40:44 +01:00
|
|
|
R_UNLESS(npdm->magic == Npdm::Magic, ResultInvalidMeta());
|
2019-06-26 23:46:19 +01:00
|
|
|
|
|
|
|
/* Validate flags. */
|
2019-10-24 09:40:44 +01:00
|
|
|
u32 mask = ~0x1F;
|
|
|
|
if (hos::GetVersion() < hos::Version_700) {
|
|
|
|
/* 7.0.0 added 0x10 as a valid bit to NPDM flags, so before that we only check 0xF. */
|
|
|
|
mask = ~0xF;
|
2019-06-26 23:46:19 +01:00
|
|
|
}
|
2019-10-24 09:40:44 +01:00
|
|
|
R_UNLESS(!(npdm->flags & mask), ResultInvalidMeta());
|
2019-06-26 23:46:19 +01:00
|
|
|
|
|
|
|
/* Validate Acid extents. */
|
|
|
|
R_TRY(ValidateSubregion(sizeof(Npdm), size, npdm->acid_offset, npdm->acid_size, sizeof(Acid)));
|
|
|
|
|
|
|
|
/* Validate Aci extends. */
|
|
|
|
R_TRY(ValidateSubregion(sizeof(Npdm), size, npdm->aci_offset, npdm->aci_size, sizeof(Aci)));
|
|
|
|
|
2019-10-24 09:40:44 +01:00
|
|
|
return ResultSuccess();
|
2019-06-26 23:46:19 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
Result ValidateAcid(const Acid *acid, size_t size) {
|
|
|
|
/* Validate magic. */
|
2019-10-24 09:40:44 +01:00
|
|
|
R_UNLESS(acid->magic == Acid::Magic, ResultInvalidMeta());
|
2019-06-26 23:46:19 +01:00
|
|
|
|
|
|
|
/* TODO: Check if retail flag is set if not development hardware. */
|
|
|
|
|
|
|
|
/* Validate Fac, Sac, Kac. */
|
|
|
|
R_TRY(ValidateSubregion(sizeof(Acid), size, acid->fac_offset, acid->fac_size));
|
|
|
|
R_TRY(ValidateSubregion(sizeof(Acid), size, acid->sac_offset, acid->sac_size));
|
|
|
|
R_TRY(ValidateSubregion(sizeof(Acid), size, acid->kac_offset, acid->kac_size));
|
|
|
|
|
2019-10-24 09:40:44 +01:00
|
|
|
return ResultSuccess();
|
2019-06-26 23:46:19 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
Result ValidateAci(const Aci *aci, size_t size) {
|
|
|
|
/* Validate magic. */
|
2019-10-24 09:40:44 +01:00
|
|
|
R_UNLESS(aci->magic == Aci::Magic, ResultInvalidMeta());
|
2019-06-26 23:46:19 +01:00
|
|
|
|
|
|
|
/* Validate Fah, Sac, Kac. */
|
|
|
|
R_TRY(ValidateSubregion(sizeof(Aci), size, aci->fah_offset, aci->fah_size));
|
|
|
|
R_TRY(ValidateSubregion(sizeof(Aci), size, aci->sac_offset, aci->sac_size));
|
|
|
|
R_TRY(ValidateSubregion(sizeof(Aci), size, aci->kac_offset, aci->kac_size));
|
|
|
|
|
2019-10-24 09:40:44 +01:00
|
|
|
return ResultSuccess();
|
2019-06-26 23:46:19 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
Result LoadMetaFromFile(FILE *f, MetaCache *cache) {
|
|
|
|
/* Reset cache. */
|
|
|
|
cache->meta = {};
|
|
|
|
|
|
|
|
/* Read from file. */
|
|
|
|
size_t npdm_size = 0;
|
|
|
|
{
|
|
|
|
/* Get file size. */
|
|
|
|
fseek(f, 0, SEEK_END);
|
|
|
|
npdm_size = ftell(f);
|
|
|
|
fseek(f, 0, SEEK_SET);
|
|
|
|
|
|
|
|
/* Read data into cache buffer. */
|
2019-10-24 09:40:44 +01:00
|
|
|
R_UNLESS(npdm_size <= MetaCacheBufferSize, ResultTooLargeMeta());
|
|
|
|
R_UNLESS(fread(cache->buffer, npdm_size, 1, f) == 1, ResultTooLargeMeta());
|
2019-06-26 23:46:19 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Ensure size is big enough. */
|
2019-10-24 09:40:44 +01:00
|
|
|
R_UNLESS(npdm_size >= sizeof(Npdm), ResultInvalidMeta());
|
2019-06-26 23:46:19 +01:00
|
|
|
|
|
|
|
/* Validate the meta. */
|
|
|
|
{
|
|
|
|
Meta *meta = &cache->meta;
|
|
|
|
|
|
|
|
Npdm *npdm = reinterpret_cast<Npdm *>(cache->buffer);
|
|
|
|
R_TRY(ValidateNpdm(npdm, npdm_size));
|
|
|
|
|
|
|
|
Acid *acid = reinterpret_cast<Acid *>(cache->buffer + npdm->acid_offset);
|
|
|
|
Aci *aci = reinterpret_cast<Aci *>(cache->buffer + npdm->aci_offset);
|
|
|
|
R_TRY(ValidateAcid(acid, npdm->acid_size));
|
|
|
|
R_TRY(ValidateAci(aci, npdm->aci_size));
|
|
|
|
|
|
|
|
/* Set Meta members. */
|
|
|
|
meta->npdm = npdm;
|
|
|
|
meta->acid = acid;
|
|
|
|
meta->aci = aci;
|
|
|
|
|
|
|
|
meta->acid_fac = reinterpret_cast<u8 *>(acid) + acid->fac_offset;
|
|
|
|
meta->acid_sac = reinterpret_cast<u8 *>(acid) + acid->sac_offset;
|
|
|
|
meta->acid_kac = reinterpret_cast<u8 *>(acid) + acid->kac_offset;
|
|
|
|
|
|
|
|
meta->aci_fah = reinterpret_cast<u8 *>(aci) + aci->fah_offset;
|
|
|
|
meta->aci_sac = reinterpret_cast<u8 *>(aci) + aci->sac_offset;
|
|
|
|
meta->aci_kac = reinterpret_cast<u8 *>(aci) + aci->kac_offset;
|
|
|
|
}
|
|
|
|
|
2019-10-24 09:40:44 +01:00
|
|
|
return ResultSuccess();
|
2019-06-26 23:46:19 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/* API. */
|
2019-11-21 12:03:19 +00:00
|
|
|
Result LoadMeta(Meta *out_meta, ncm::ProgramId program_id, const cfg::OverrideStatus &status) {
|
2019-06-26 23:46:19 +01:00
|
|
|
FILE *f = nullptr;
|
|
|
|
|
|
|
|
/* Try to load meta from file. */
|
2019-11-21 12:03:19 +00:00
|
|
|
R_TRY(OpenCodeFile(f, program_id, status, MetaFilePath));
|
2019-06-26 23:46:19 +01:00
|
|
|
{
|
|
|
|
ON_SCOPE_EXIT { fclose(f); };
|
|
|
|
R_TRY(LoadMetaFromFile(f, &g_meta_cache));
|
|
|
|
}
|
|
|
|
|
2019-10-28 04:43:01 +00:00
|
|
|
/* Patch meta. Start by setting all program ids to the current program id. */
|
2019-06-26 23:46:19 +01:00
|
|
|
Meta *meta = &g_meta_cache.meta;
|
2019-10-28 04:43:01 +00:00
|
|
|
meta->acid->program_id_min = program_id;
|
|
|
|
meta->acid->program_id_max = program_id;
|
|
|
|
meta->aci->program_id = program_id;
|
2019-06-26 23:46:19 +01:00
|
|
|
|
|
|
|
/* For HBL, we need to copy some information from the base meta. */
|
2019-11-21 12:03:19 +00:00
|
|
|
if (status.IsHbl()) {
|
|
|
|
if (R_SUCCEEDED(OpenCodeFileFromBaseExefs(f, program_id, status, MetaFilePath))) {
|
2019-06-26 23:46:19 +01:00
|
|
|
ON_SCOPE_EXIT { fclose(f); };
|
|
|
|
if (R_SUCCEEDED(LoadMetaFromFile(f, &g_original_meta_cache))) {
|
|
|
|
Meta *o_meta = &g_original_meta_cache.meta;
|
|
|
|
|
|
|
|
/* Fix pool partition. */
|
2019-10-15 05:40:05 +01:00
|
|
|
if (hos::GetVersion() >= hos::Version_500) {
|
2019-06-26 23:46:19 +01:00
|
|
|
meta->acid->flags = (meta->acid->flags & 0xFFFFFFC3) | (o_meta->acid->flags & 0x0000003C);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Fix flags. */
|
2019-06-28 01:37:33 +01:00
|
|
|
const u16 program_info_flags = caps::GetProgramInfoFlags(o_meta->aci_kac, o_meta->aci->kac_size);
|
|
|
|
caps::SetProgramInfoFlags(program_info_flags, meta->acid_kac, meta->acid->kac_size);
|
|
|
|
caps::SetProgramInfoFlags(program_info_flags, meta->aci_kac, meta->aci->kac_size);
|
2019-06-26 23:46:19 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Set output. */
|
2019-10-28 04:43:01 +00:00
|
|
|
g_cached_program_id = program_id;
|
2019-11-21 12:03:19 +00:00
|
|
|
g_cached_override_status = status;
|
2019-06-26 23:46:19 +01:00
|
|
|
*out_meta = *meta;
|
|
|
|
|
2019-10-24 09:40:44 +01:00
|
|
|
return ResultSuccess();
|
2019-06-26 23:46:19 +01:00
|
|
|
}
|
|
|
|
|
2019-11-21 12:03:19 +00:00
|
|
|
Result LoadMetaFromCache(Meta *out_meta, ncm::ProgramId program_id, const cfg::OverrideStatus &status) {
|
|
|
|
if (g_cached_program_id != program_id || g_cached_override_status != status) {
|
|
|
|
return LoadMeta(out_meta, program_id, status);
|
2019-06-26 23:46:19 +01:00
|
|
|
}
|
|
|
|
*out_meta = g_meta_cache.meta;
|
2019-10-24 09:40:44 +01:00
|
|
|
return ResultSuccess();
|
2019-06-26 23:46:19 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void InvalidateMetaCache() {
|
2019-10-28 04:43:01 +00:00
|
|
|
/* Set the cached program id back to zero. */
|
|
|
|
g_cached_program_id = {};
|
2019-06-26 23:46:19 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|