Implement -t hfs0, Re-work --outdir override.

This commit is contained in:
Michael Scire 2018-02-03 22:09:16 -08:00
parent ac7b24a24e
commit 52f45fbd60
10 changed files with 300 additions and 15 deletions

View File

@ -17,7 +17,7 @@ all:
.c.o:
$(CC) $(INCLUDE) -c $(CFLAGS) -o $@ $<
hactool: sha.o aes.o rsa.o npdm.o bktr.o pki.o pfs0.o romfs.o utils.o nca.o main.o filepath.o
hactool: sha.o aes.o rsa.o npdm.o bktr.o pki.o pfs0.o hfs0.o romfs.o utils.o nca.o main.o filepath.o
$(CC) -o $@ $^ $(LDFLAGS) -L $(LIBDIR)
aes.o: aes.h types.h
@ -26,6 +26,8 @@ bktr.o: bktr.h types.h
filepath.o: filepath.c types.h
hfs0.o: hfs0.h types.h
main.o: main.c pki.h types.h
pfs0.o: pfs0.h types.h

View File

@ -18,7 +18,7 @@ Options:
-r, --raw Keep raw data, don't unpack.
-y, --verify Verify hashes and signatures.
-d, --dev Decrypt with development keys instead of retail.
-t, --intype=type Specify input file type [nca, pfs0]
-t, --intype=type Specify input file type [nca, pfs0, romfs]
--titlekey=key Set title key for Rights ID crypto titles.
--contentkey=key Set raw key for NCA body decryption.
NCA options:
@ -40,7 +40,17 @@ NCA options:
--baseromfs Set Base RomFS to use with update partitions.
--basenca Set Base NCA to use with update partitions.
PFS0 options:
--outdir=dir Specify PFS0 directory path.
--pfs0dir=dir Specify PFS0 directory path.
--outdir=dir Specify PFS0 directory path. Overrides previous path, if present.
--exefsdir=dir Specify PFS0 directory path. Overrides previous paths, if present for ExeFS PFS0.
RomFS options:
--romfsdir=dir Specify RomFS directory path.
--outdir=dir Specify RomFS directory path. Overrides previous path, if present.
--listromfs List files in RomFS.
HFS0 options:
--hfs0dir=dir Specify HFS0 directory path.
--outdir=dir Specify HFS0 directory path. Overrides previous path, if present.
--exefsdir=dir Specify HFS0 directory path. Overrides previous paths, if present.
```
## Building

109
hfs0.c Normal file
View File

@ -0,0 +1,109 @@
#include <string.h>
#include "hfs0.h"
void hfs0_process(hfs0_ctx_t *ctx) {
/* Read *just* safe amount. */
hfs0_header_t raw_header;
fseeko64(ctx->file, ctx->offset, SEEK_SET);
if (fread(&raw_header, 1, sizeof(raw_header), ctx->file) != sizeof(raw_header)) {
fprintf(stderr, "Failed to read HFS0 header!\n");
exit(EXIT_FAILURE);
}
if (raw_header.magic != MAGIC_HFS0) {
printf("Error: HFS0 is corrupt!\n");
exit(EXIT_FAILURE);
}
uint64_t header_size = hfs0_get_header_size(&raw_header);
ctx->header = malloc(header_size);
if (ctx->header == NULL) {
fprintf(stderr, "Failed to allocate HFS0 header!\n");
exit(EXIT_FAILURE);
}
fseeko64(ctx->file, ctx->offset, SEEK_SET);
if (fread(ctx->header, 1, header_size, ctx->file) != header_size) {
fprintf(stderr, "Failed to read HFS0 header!\n");
exit(EXIT_FAILURE);
}
/* Weak file validation. */
uint64_t max_size = 0x1ULL;
max_size <<= 48; /* Switch file sizes are capped at 48 bits. */
uint64_t cur_ofs = 0;
for (unsigned int i = 0; i < ctx->header->num_files; i++) {
hfs0_file_entry_t *cur_file = hfs0_get_file_entry(ctx->header, i);
if (cur_file->offset != cur_ofs) {
printf("Error: HFS0 is corrupt!\n");
exit(EXIT_FAILURE);
}
cur_ofs += cur_file->size;
}
if (ctx->tool_ctx->action & ACTION_INFO) {
hfs0_print(ctx);
}
if (ctx->tool_ctx->action & ACTION_EXTRACT) {
hfs0_save(ctx);
}
}
void hfs0_save_file(hfs0_ctx_t *ctx, uint32_t i, filepath_t *dirpath) {
if (i >= ctx->header->num_files) {
fprintf(stderr, "Could not save file %"PRId32"!\n", i);
exit(EXIT_FAILURE);
}
hfs0_file_entry_t *cur_file = hfs0_get_file_entry(ctx->header, i);
if (strlen(hfs0_get_file_name(ctx->header, i)) >= MAX_PATH - strlen(dirpath->char_path) - 1) {
fprintf(stderr, "Filename too long in HFS0!\n");
exit(EXIT_FAILURE);
}
filepath_t filepath;
filepath_copy(&filepath, dirpath);
filepath_append(&filepath, "%s", hfs0_get_file_name(ctx->header, i));
printf("Saving %s to %s...\n", hfs0_get_file_name(ctx->header, i), filepath.char_path);
uint64_t ofs = hfs0_get_header_size(ctx->header) + cur_file->offset;
save_file_section(ctx->file, ctx->offset + ofs, cur_file->size, &filepath);
}
void hfs0_save(hfs0_ctx_t *ctx) {
/* Extract to directory. */
filepath_t *dirpath = NULL;
if (ctx->tool_ctx->file_type == FILETYPE_HFS0 && ctx->tool_ctx->settings.out_dir_path.enabled) {
dirpath = &ctx->tool_ctx->settings.out_dir_path.path;
}
if (dirpath == NULL || dirpath->valid != VALIDITY_VALID) {
dirpath = &ctx->tool_ctx->settings.hfs0_dir_path;
}
if (dirpath != NULL && dirpath->valid == VALIDITY_VALID) {
os_makedir(dirpath->os_path);
for (uint32_t i = 0; i < ctx->header->num_files; i++) {
hfs0_save_file(ctx, i, dirpath);
}
}
}
void hfs0_print(hfs0_ctx_t *ctx) {
printf("\nHFS0:\n");
print_magic("Magic: ", ctx->header->magic);
printf("Number of files: %"PRId32"\n", ctx->header->num_files);
if (ctx->header->num_files > 0) {
printf("Files:");
for (unsigned int i = 0; i < ctx->header->num_files; i++) {
hfs0_file_entry_t *cur_file = hfs0_get_file_entry(ctx->header, i);
if (ctx->tool_ctx->action & ACTION_VERIFY) {
validity_t hash_validity = check_memory_hash_table(ctx->file, cur_file->hash, ctx->offset + hfs0_get_header_size(ctx->header) + cur_file->offset, cur_file->hashed_size, cur_file->hashed_size, 0);
printf("%s%s:/%-48s %012"PRIx64"-%012"PRIx64" (%s)\n", i == 0 ? " " : " ", ctx->name == NULL ? "hfs0" : ctx->name, hfs0_get_file_name(ctx->header, i), cur_file->offset, cur_file->offset + cur_file->size, GET_VALIDITY_STR(hash_validity));
} else {
printf("%s%s:/%-48s %012"PRIx64"-%012"PRIx64"\n", i == 0 ? " " : " ", ctx->name == NULL ? "hfs0" : ctx->name, hfs0_get_file_name(ctx->header, i), cur_file->offset, cur_file->offset + cur_file->size);
}
}
}
}

57
hfs0.h Normal file
View File

@ -0,0 +1,57 @@
#ifndef HACTOOL_hfs0_H
#define HACTOOL_hfs0_H
#include "types.h"
#include "utils.h"
#include "settings.h"
#define MAGIC_HFS0 0x30534648
typedef struct {
uint32_t magic;
uint32_t num_files;
uint32_t string_table_size;
uint32_t reserved;
} hfs0_header_t;
typedef struct {
uint64_t offset;
uint64_t size;
uint32_t string_table_offset;
uint32_t hashed_size;
uint64_t reserved;
unsigned char hash[0x20];
} hfs0_file_entry_t;
typedef struct {
FILE *file;
uint64_t offset;
uint64_t size;
hactool_ctx_t *tool_ctx;
hfs0_header_t *header;
char *name;
} hfs0_ctx_t;
static inline hfs0_file_entry_t *hfs0_get_file_entry(hfs0_header_t *hdr, uint32_t i) {
if (i >= hdr->num_files) return NULL;
return (hfs0_file_entry_t *)((char *)(hdr) + sizeof(*hdr) + i * sizeof(hfs0_file_entry_t));
}
static inline char *hfs0_get_string_table(hfs0_header_t *hdr) {
return (char *)(hdr) + sizeof(*hdr) + hdr->num_files * sizeof(hfs0_file_entry_t);
}
static inline uint64_t hfs0_get_header_size(hfs0_header_t *hdr) {
return sizeof(*hdr) + hdr->num_files * sizeof(hfs0_file_entry_t) + hdr->string_table_size;
}
static inline char *hfs0_get_file_name(hfs0_header_t *hdr, uint32_t i) {
return hfs0_get_string_table(hdr) + hfs0_get_file_entry(hdr, i)->string_table_offset;
}
void hfs0_process(hfs0_ctx_t *ctx);
void hfs0_save(hfs0_ctx_t *ctx);
void hfs0_print(hfs0_ctx_t *ctx);
#endif

43
main.c
View File

@ -8,6 +8,7 @@
#include "settings.h"
#include "pki.h"
#include "nca.h"
#include "hfs0.h"
static char *prog_name = "hactool";
@ -48,10 +49,17 @@ static void usage(void) {
" --baseromfs Set Base RomFS to use with update partitions.\n"
" --basenca Set Base NCA to use with update partitions.\n"
"PFS0 options:\n"
" --outdir=dir Specify PFS0 directory path.\n"
" --pfs0dir=dir Specify PFS0 directory path.\n"
" --outdir=dir Specify PFS0 directory path. Overrides previous path, if present.\n"
" --exefsdir=dir Specify PFS0 directory path. Overrides previous paths, if present for ExeFS PFS0.\n"
"RomFS options:\n"
" --romfsdir=dir Specify RomFS directory path.\n"
" --outdir=dir Specify RomFS directory path. Overrides previous path, if present.\n"
" --listromfs List files in RomFS.\n"
"HFS0 options:\n"
" --hfs0dir=dir Specify HFS0 directory path.\n"
" --outdir=dir Specify HFS0 directory path. Overrides previous path, if present.\n"
" --exefsdir=dir Specify HFS0 directory path. Overrides previous paths, if present.\n"
"\n", __TIME__, __DATE__, prog_name);
exit(EXIT_FAILURE);
}
@ -145,6 +153,8 @@ int main(int argc, char **argv) {
{"outdir", 1, NULL, 17},
{"plaintext", 1, NULL, 18},
{"header", 1, NULL, 19},
{"pfs0dir", 1, NULL, 20},
{"hfs0dir", 1, NULL, 21},
{NULL, 0, NULL, 0},
};
@ -175,12 +185,11 @@ int main(int argc, char **argv) {
} else if (!strcmp(optarg, "pfs0") || !strcmp(optarg, "exefs")) {
nca_ctx.tool_ctx->file_type = FILETYPE_PFS0;
} else if (!strcmp(optarg, "romfs")) {
nca_ctx.tool_ctx->file_type = FILETYPE_ROMFS;
}
/* } else if (!strcmp(optarg, "hfs0")) {
* nca_ctx.tool_ctx->file_type = FILETYPE_HFS0;
* }
* } else if (!strcmp(optarg, "xci") || !strcmp(optarg, "gamecard") || !strcmp(optarg, "gc")) {
nca_ctx.tool_ctx->file_type = FILETYPE_ROMFS;
} else if (!strcmp(optarg, "hfs0")) {
nca_ctx.tool_ctx->file_type = FILETYPE_HFS0;
}
/* } else if (!strcmp(optarg, "xci") || !strcmp(optarg, "gamecard") || !strcmp(optarg, "gc")) {
* nca_ctx.tool_ctx->file_type = FILETYPE_XCI;
* }
* } else if (!strcmp(optarg, "package2") || !strcmp(optarg, "pk21")) {
@ -258,7 +267,8 @@ int main(int argc, char **argv) {
nca_ctx.tool_ctx->base_nca_ctx->file = base_ctx.file;
break;
case 17:
filepath_set(&nca_ctx.tool_ctx->settings.out_dir_path, optarg);
tool_ctx.settings.out_dir_path.enabled = 1;
filepath_set(&tool_ctx.settings.out_dir_path.path, optarg);
break;
case 18:
filepath_set(&nca_ctx.tool_ctx->settings.dec_nca_path, optarg);
@ -266,6 +276,12 @@ int main(int argc, char **argv) {
case 19:
filepath_set(&nca_ctx.tool_ctx->settings.header_path, optarg);
break;
case 20:
filepath_set(&tool_ctx.settings.pfs0_dir_path, optarg);
break;
case 21:
filepath_set(&tool_ctx.settings.hfs0_dir_path, optarg);
break;
default:
usage();
return EXIT_FAILURE;
@ -344,6 +360,17 @@ int main(int argc, char **argv) {
}
break;
}
case FILETYPE_HFS0: {
hfs0_ctx_t hfs0_ctx;
memset(&hfs0_ctx, 0, sizeof(hfs0_ctx));
hfs0_ctx.file = tool_ctx.file;
hfs0_ctx.tool_ctx = &tool_ctx;
hfs0_process(&hfs0_ctx);
if (hfs0_ctx.header) {
free(hfs0_ctx.header);
}
break;
}
default: {
fprintf(stderr, "Unknown File Type!\n\n");
usage();

5
pfs0.c
View File

@ -101,8 +101,11 @@ void pfs0_save(pfs0_ctx_t *ctx) {
if (ctx->is_exefs && ctx->tool_ctx->settings.exefs_dir_path.enabled) {
dirpath = &ctx->tool_ctx->settings.exefs_dir_path.path;
}
if ((dirpath == NULL || dirpath->valid != VALIDITY_VALID) && (ctx->tool_ctx->file_type == FILETYPE_PFS0 && ctx->tool_ctx->settings.out_dir_path.enabled)) {
dirpath = &ctx->tool_ctx->settings.out_dir_path.path;
}
if (dirpath == NULL || dirpath->valid != VALIDITY_VALID) {
dirpath = &ctx->tool_ctx->settings.out_dir_path;
dirpath = &ctx->tool_ctx->settings.pfs0_dir_path;
}
if (dirpath != NULL && dirpath->valid == VALIDITY_VALID) {
os_makedir(dirpath->os_path);

View File

@ -124,6 +124,9 @@ void romfs_save(romfs_ctx_t *ctx) {
if (ctx->tool_ctx->settings.romfs_dir_path.enabled) {
dirpath = &ctx->tool_ctx->settings.romfs_dir_path.path;
}
if ((dirpath == NULL || dirpath->valid != VALIDITY_VALID) && (ctx->tool_ctx->file_type == FILETYPE_ROMFS && ctx->tool_ctx->settings.out_dir_path.enabled)) {
dirpath = &ctx->tool_ctx->settings.out_dir_path.path;
}
if (dirpath != NULL && dirpath->valid == VALIDITY_VALID) {
os_makedir(dirpath->os_path);
romfs_visit_dir(ctx, 0, dirpath);

View File

@ -49,7 +49,9 @@ typedef struct {
override_filepath_t exefs_dir_path;
override_filepath_t romfs_path;
override_filepath_t romfs_dir_path;
filepath_t out_dir_path;
override_filepath_t out_dir_path;
filepath_t pfs0_dir_path;
filepath_t hfs0_dir_path;
filepath_t dec_nca_path;
filepath_t header_path;
} hactool_settings_t;
@ -59,7 +61,7 @@ enum hactool_file_type
FILETYPE_NCA,
FILETYPE_PFS0,
FILETYPE_ROMFS,
/* FILETYPE_HFS0, */
FILETYPE_HFS0,
/* FILETYPE_XCI, */
/* FILETYPE_PACKAGE2, */
/* FILETYPE_PACKAGE1, */

71
utils.c
View File

@ -6,6 +6,7 @@
#endif
#include "utils.h"
#include "filepath.h"
#include "sha.h"
uint32_t align(uint32_t offset, uint32_t alignment) {
uint32_t mask = ~(alignment-1);
@ -73,7 +74,7 @@ void save_file_section(FILE *f_in, uint64_t ofs, uint64_t total_size, filepath_t
}
memset(buf, 0xCC, read_size); /* Debug in case I fuck this up somehow... */
uint64_t end_ofs = ofs + total_size;
fseek(f_in, ofs, SEEK_SET);
fseeko64(f_in, ofs, SEEK_SET);
while (ofs < end_ofs) {
if (ofs + read_size >= end_ofs) read_size = end_ofs - ofs;
if (fread(buf, 1, read_size, f_in) != read_size) {
@ -88,3 +89,71 @@ void save_file_section(FILE *f_in, uint64_t ofs, uint64_t total_size, filepath_t
free(buf);
}
validity_t check_memory_hash_table(FILE *f_in, unsigned char *hash_table, uint64_t data_ofs, uint64_t data_len, uint64_t block_size, int full_block) {
if (block_size == 0) {
/* Block size of 0 is always invalid. */
return VALIDITY_INVALID;
}
unsigned char cur_hash[0x20];
uint64_t read_size = block_size;
unsigned char *block = malloc(block_size);
if (block == NULL) {
fprintf(stderr, "Failed to allocate hash block!\n");
exit(EXIT_FAILURE);
}
validity_t result = VALIDITY_VALID;
unsigned char *cur_hash_table_entry = hash_table;
for (uint64_t ofs = 0; ofs < data_len; ofs += read_size) {
fseeko64(f_in, ofs + data_ofs, SEEK_SET);
if (ofs + read_size > data_len) {
/* Last block... */
memset(block, 0, read_size);
read_size = data_len - ofs;
}
if (fread(block, 1, read_size, f_in) != read_size) {
fprintf(stderr, "Failed to read file!\n");
exit(EXIT_FAILURE);
}
sha256_hash_buffer(cur_hash, block, full_block ? block_size : read_size);
if (memcmp(cur_hash, cur_hash_table_entry, 0x20) != 0) {
result = VALIDITY_INVALID;
break;
}
cur_hash_table_entry += 0x20;
}
free(block);
return result;
}
validity_t check_file_hash_table(FILE *f_in, uint64_t hash_ofs, uint64_t data_ofs, uint64_t data_len, uint64_t block_size, int full_block) {
if (block_size == 0) {
/* Block size of 0 is always invalid. */
return VALIDITY_INVALID;
}
uint64_t hash_table_size = data_len / block_size;
if (data_len % block_size) hash_table_size++;
hash_table_size *= 0x20;
unsigned char *hash_table = malloc(hash_table_size);
if (hash_table == NULL) {
fprintf(stderr, "Failed to allocate hash table!\n");
exit(EXIT_FAILURE);
}
fseeko64(f_in, hash_ofs, SEEK_SET);
if (fread(hash_table, 1, hash_table_size, f_in) != hash_table_size) {
fprintf(stderr, "Failed to read file!\n");
exit(EXIT_FAILURE);
}
validity_t result = check_memory_hash_table(f_in, hash_table, data_ofs, data_len, block_size, full_block);
free(hash_table);
return result;
}

View File

@ -37,6 +37,9 @@ uint64_t _fsize(const char *filename);
void save_file_section(FILE *f_in, uint64_t ofs, uint64_t total_size, struct filepath *filepath);
validity_t check_memory_hash_table(FILE *f_in, unsigned char *hash_table, uint64_t data_ofs, uint64_t data_len, uint64_t block_size, int full_block);
validity_t check_file_hash_table(FILE *f_in, uint64_t hash_ofs, uint64_t data_ofs, uint64_t data_len, uint64_t block_size, int full_block);
#ifdef _MSC_VER
inline int fseeko64(FILE *__stream, long long __off, int __whence)
{