1
0
Fork 0
mirror of https://github.com/DarkMatterCore/nxdumptool.git synced 2024-09-20 05:53:25 +01:00
nxdumptool/source/nso.c
Pablo Curiel 40fc21b5a3 Fix ProgramInfo and NSO issues.
XML generation confirmed to be working. The new algorithm faithfully reproduces the same output from legacy nxdumptool with much less overhead and memory usage.
2020-10-11 20:40:54 -04:00

298 lines
13 KiB
C

/*
* nso.c
*
* Copyright (c) 2020, DarkMatterCore <pabloacurielz@gmail.com>.
*
* This file is part of nxdumptool (https://github.com/DarkMatterCore/nxdumptool).
*
* nxdumptool 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.
*
* nxdumptool 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/>.
*/
#define LZ4_STATIC_LINKING_ONLY /* Required by LZ4 to enable in-place decompression. */
#include "utils.h"
#include "nso.h"
#include "lz4.h"
/* Function prototypes. */
static bool nsoGetModuleName(NsoContext *nso_ctx);
static u8 *nsoGetRodataSegment(NsoContext *nso_ctx);
static bool nsoGetModuleInfoName(NsoContext *nso_ctx, u8 *rodata_buf);
static bool nsoGetSectionFromRodataSegment(NsoContext *nso_ctx, u8 *rodata_buf, u8 **section_ptr, u64 section_offset, u64 section_size);
bool nsoInitializeContext(NsoContext *out, PartitionFileSystemContext *pfs_ctx, PartitionFileSystemEntry *pfs_entry)
{
NcaContext *nca_ctx = NULL;
u8 *rodata_buf = NULL;
bool success = false;
if (!out || !pfs_ctx || !pfs_ctx->nca_fs_ctx || !(nca_ctx = (NcaContext*)pfs_ctx->nca_fs_ctx->nca_ctx) || nca_ctx->content_type != NcmContentType_Program || !pfs_ctx->offset || !pfs_ctx->size || \
!pfs_ctx->is_exefs || pfs_ctx->header_size <= sizeof(PartitionFileSystemHeader) || !pfs_ctx->header || !pfs_entry)
{
LOGFILE("Invalid parameters!");
return false;
}
/* Free output context beforehand. */
nsoFreeContext(out);
/* Update output context. */
out->pfs_ctx = pfs_ctx;
out->pfs_entry = pfs_entry;
/* Get entry filename. */
if (!(out->nso_filename = pfsGetEntryName(pfs_ctx, pfs_entry)) || !strlen(out->nso_filename))
{
LOGFILE("Invalid Partition FS entry filename!");
goto end;
}
/* Read NSO header. */
if (!pfsReadEntryData(pfs_ctx, pfs_entry, &(out->nso_header), sizeof(NsoHeader), 0))
{
LOGFILE("Failed to read NSO \"%s\" header!", out->nso_filename);;
goto end;
}
/* Verify NSO header. */
if (__builtin_bswap32(out->nso_header.magic) != NSO_HEADER_MAGIC)
{
LOGFILE("Invalid NSO \"%s\" header magic word! (0x%08X != 0x%08X).", out->nso_filename, __builtin_bswap32(out->nso_header.magic), __builtin_bswap32(NSO_HEADER_MAGIC));
goto end;
}
if (out->nso_header.text_segment_header.file_offset < sizeof(NsoHeader) || !out->nso_header.text_segment_header.size || \
((out->nso_header.flags & NsoFlags_TextCompress) && (!out->nso_header.text_file_size || out->nso_header.text_file_size > out->nso_header.text_segment_header.size)) || \
(!(out->nso_header.flags & NsoFlags_TextCompress) && out->nso_header.text_file_size != out->nso_header.text_segment_header.size) || \
(out->nso_header.text_segment_header.file_offset + out->nso_header.text_file_size) > pfs_entry->size)
{
LOGFILE("Invalid .text segment offset/size for NSO \"%s\"! (0x%08X, 0x%08X, 0x%08X).", out->nso_filename, out->nso_header.text_segment_header.file_offset, out->nso_header.text_file_size, \
out->nso_header.text_segment_header.size);
goto end;
}
if (out->nso_header.rodata_segment_header.file_offset < sizeof(NsoHeader) || !out->nso_header.rodata_segment_header.size || \
((out->nso_header.flags & NsoFlags_RoCompress) && (!out->nso_header.rodata_file_size || out->nso_header.rodata_file_size > out->nso_header.rodata_segment_header.size)) || \
(!(out->nso_header.flags & NsoFlags_RoCompress) && out->nso_header.rodata_file_size != out->nso_header.rodata_segment_header.size) || \
(out->nso_header.rodata_segment_header.file_offset + out->nso_header.rodata_file_size) > pfs_entry->size)
{
LOGFILE("Invalid .rodata segment offset/size for NSO \"%s\"! (0x%08X, 0x%08X, 0x%08X).", out->nso_filename, out->nso_header.rodata_segment_header.file_offset, out->nso_header.rodata_file_size, \
out->nso_header.rodata_segment_header.size);
goto end;
}
if (out->nso_header.data_segment_header.file_offset < sizeof(NsoHeader) || !out->nso_header.data_segment_header.size || \
((out->nso_header.flags & NsoFlags_DataCompress) && (!out->nso_header.data_file_size || out->nso_header.data_file_size > out->nso_header.data_segment_header.size)) || \
(!(out->nso_header.flags & NsoFlags_DataCompress) && out->nso_header.data_file_size != out->nso_header.data_segment_header.size) || \
(out->nso_header.data_segment_header.file_offset + out->nso_header.data_file_size) > pfs_entry->size)
{
LOGFILE("Invalid .data segment offset/size for NSO \"%s\"! (0x%08X, 0x%08X, 0x%08X).", out->nso_filename, out->nso_header.data_segment_header.file_offset, out->nso_header.data_file_size, \
out->nso_header.data_segment_header.size);
goto end;
}
if (out->nso_header.module_name_offset < sizeof(NsoHeader) || !out->nso_header.module_name_size || (out->nso_header.module_name_offset + out->nso_header.module_name_size) > pfs_entry->size)
{
LOGFILE("Invalid module name offset/size for NSO \"%s\"! (0x%08X, 0x%08X).", out->nso_filename, out->nso_header.module_name_offset, out->nso_header.module_name_size);
goto end;
}
if (out->nso_header.api_info_section_header.size && (out->nso_header.api_info_section_header.offset + out->nso_header.api_info_section_header.size) > out->nso_header.rodata_segment_header.size)
{
LOGFILE("Invalid .api_info section offset/size for NSO \"%s\"! (0x%08X, 0x%08X).", out->nso_filename, out->nso_header.api_info_section_header.offset, out->nso_header.api_info_section_header.size);
goto end;
}
if (!out->nso_header.dynstr_section_header.size || (out->nso_header.dynstr_section_header.offset + out->nso_header.dynstr_section_header.size) > out->nso_header.rodata_segment_header.size)
{
LOGFILE("Invalid .dynstr section offset/size for NSO \"%s\"! (0x%08X, 0x%08X).", out->nso_filename, out->nso_header.dynstr_section_header.offset, out->nso_header.dynstr_section_header.size);
goto end;
}
if (!out->nso_header.dynsym_section_header.size || (out->nso_header.dynsym_section_header.offset + out->nso_header.dynsym_section_header.size) > out->nso_header.rodata_segment_header.size)
{
LOGFILE("Invalid .dynsym section offset/size for NSO \"%s\"! (0x%08X, 0x%08X).", out->nso_filename, out->nso_header.dynsym_section_header.offset, out->nso_header.dynsym_section_header.size);
goto end;
}
/* Get module name. */
if (!nsoGetModuleName(out)) goto end;
/* Get .rodata segment. */
if (!(rodata_buf = nsoGetRodataSegment(out))) goto end;
/* Get module info name. */
if (!nsoGetModuleInfoName(out, rodata_buf)) goto end;
/* Get .api_info section data. */
if (!nsoGetSectionFromRodataSegment(out, rodata_buf, (u8**)&(out->rodata_api_info_section), out->nso_header.api_info_section_header.offset, out->nso_header.api_info_section_header.size)) goto end;
out->rodata_api_info_section_size = out->nso_header.api_info_section_header.size;
/* Get .dynstr section data. */
if (!nsoGetSectionFromRodataSegment(out, rodata_buf, (u8**)&(out->rodata_dynstr_section), out->nso_header.dynstr_section_header.offset, out->nso_header.dynstr_section_header.size)) goto end;
out->rodata_dynstr_section_size = out->nso_header.dynstr_section_header.size;
/* Get .dynsym section data. */
if (!nsoGetSectionFromRodataSegment(out, rodata_buf, &(out->rodata_dynsym_section), out->nso_header.dynsym_section_header.offset, out->nso_header.dynsym_section_header.size)) goto end;
out->rodata_dynsym_section_size = out->nso_header.dynsym_section_header.size;
success = true;
end:
if (rodata_buf) free(rodata_buf);
if (!success) nsoFreeContext(out);
return success;
}
static bool nsoGetModuleName(NsoContext *nso_ctx)
{
if (nso_ctx->nso_header.module_name_size <= 1) return true;
NsoModuleName module_name = {0};
/* Get module name. */
if (!pfsReadEntryData(nso_ctx->pfs_ctx, nso_ctx->pfs_entry, &module_name, sizeof(NsoModuleName), nso_ctx->nso_header.module_name_offset))
{
LOGFILE("Failed to read NSO \"%s\" module name length!", nso_ctx->nso_filename);
return false;
}
/* Verify module name length. */
if (module_name.name_length != ((u8)nso_ctx->nso_header.module_name_size - 1))
{
LOGFILE("NSO \"%s\" module name length mismatch! (0x%02X != 0x%02X).", nso_ctx->nso_filename, module_name.name_length, (u8)nso_ctx->nso_header.module_name_size - 1);
return false;
}
/* Allocate memory for the module name. */
nso_ctx->module_name = calloc(nso_ctx->nso_header.module_name_size, sizeof(char));
if (!nso_ctx->module_name)
{
LOGFILE("Failed to allocate memory for NSO \"%s\" module name!", nso_ctx->nso_filename);
return false;
}
/* Read module name string. */
if (!pfsReadEntryData(nso_ctx->pfs_ctx, nso_ctx->pfs_entry, nso_ctx->module_name, module_name.name_length, nso_ctx->nso_header.module_name_offset + 1))
{
LOGFILE("Failed to read NSO \"%s\" module name string!", nso_ctx->nso_filename);
return false;
}
return true;
}
static u8 *nsoGetRodataSegment(NsoContext *nso_ctx)
{
int lz4_res = 0;
bool compressed = (nso_ctx->nso_header.flags & NsoFlags_RoCompress), verify = (nso_ctx->nso_header.flags & NsoFlags_RoHash);
u8 *rodata_buf = NULL;
u64 rodata_buf_size = (compressed ? LZ4_DECOMPRESS_INPLACE_BUFFER_SIZE(nso_ctx->nso_header.rodata_segment_header.size) : nso_ctx->nso_header.rodata_segment_header.size);
u8 *rodata_read_ptr = NULL;
u64 rodata_read_size = (compressed ? nso_ctx->nso_header.rodata_file_size : nso_ctx->nso_header.rodata_segment_header.size);
u8 rodata_hash[SHA256_HASH_SIZE] = {0};
bool success = false;
/* Allocate memory for the .rodata buffer. */
if (!(rodata_buf = calloc(rodata_buf_size, sizeof(u8))))
{
LOGFILE("Failed to allocate 0x%lX bytes for the .rodata segment in NSO \"%s\"!", rodata_buf_size, nso_ctx->nso_filename);
return NULL;
}
rodata_read_ptr = (compressed ? (rodata_buf + (rodata_buf_size - nso_ctx->nso_header.rodata_file_size)) : rodata_buf);
/* Read .rodata segment data. */
if (!pfsReadEntryData(nso_ctx->pfs_ctx, nso_ctx->pfs_entry, rodata_read_ptr, rodata_read_size, nso_ctx->nso_header.rodata_segment_header.file_offset))
{
LOGFILE("Failed to read %s .rodata segment in NRO \"%s\"!", nso_ctx->nso_filename);
goto end;
}
if (compressed)
{
/* Decompress .rodata segment in-place. */
if ((lz4_res = LZ4_decompress_safe((char*)rodata_read_ptr, (char*)rodata_buf, (int)nso_ctx->nso_header.rodata_file_size, (int)rodata_buf_size)) != \
(int)nso_ctx->nso_header.rodata_segment_header.size)
{
LOGFILE("LZ4 decompression failed for NRO \"%s\"! (0x%08X).", nso_ctx->nso_filename, (u32)lz4_res);
goto end;
}
}
if (verify)
{
/* Verify .rodata segment hash. */
sha256CalculateHash(rodata_hash, rodata_buf, nso_ctx->nso_header.rodata_segment_header.size);
if (memcmp(rodata_hash, nso_ctx->nso_header.rodata_segment_hash, SHA256_HASH_SIZE) != 0)
{
LOGFILE(".rodata segment checksum mismatch for NRO \"%s\"!", nso_ctx->nso_filename);
goto end;
}
}
success = true;
end:
if (!success && rodata_buf)
{
free(rodata_buf);
rodata_buf = NULL;
}
return rodata_buf;
}
static bool nsoGetModuleInfoName(NsoContext *nso_ctx, u8 *rodata_buf)
{
NsoModuleInfo *module_info = (NsoModuleInfo*)(rodata_buf + 0x4);
if (!module_info->name_length) return true;
/* Allocate memory for the module info name. */
nso_ctx->module_info_name = calloc(module_info->name_length + 1, sizeof(char));
if (!nso_ctx->module_info_name)
{
LOGFILE("Failed to allocate memory for NSO \"%s\" module info name!", nso_ctx->nso_filename);
return false;
}
/* Copy module info name. */
sprintf(nso_ctx->module_info_name, "%.*s", (int)module_info->name_length, module_info->name);
return true;
}
static bool nsoGetSectionFromRodataSegment(NsoContext *nso_ctx, u8 *rodata_buf, u8 **section_ptr, u64 section_offset, u64 section_size)
{
if (!section_size) return true;
/* Allocate memory for the desired .rodata section. */
if (!(*section_ptr = malloc(section_size)))
{
LOGFILE("Failed to allocate 0x%lX bytes for section at .rodata offset 0x%lX in NSO \"%s\"!", section_size, section_offset, nso_ctx->nso_filename);
return false;
}
/* Copy .rodata section data. */
memcpy(*section_ptr, rodata_buf + section_offset, section_size);
return true;
}