diff --git a/ivfc.h b/ivfc.h index 4fc4e0e..489112c 100644 --- a/ivfc.h +++ b/ivfc.h @@ -37,6 +37,15 @@ typedef struct { uint8_t master_hash[0x20]; } ivfc_hdr_t; +typedef struct { + uint32_t magic; + uint32_t id; + uint32_t master_hash_size; + uint32_t num_levels; + ivfc_level_hdr_t level_headers[IVFC_MAX_LEVEL]; + uint8_t salt_source[0x20]; +} ivfc_save_hdr_t; + /* RomFS structs. */ #define ROMFS_HEADER_SIZE 0x00000050 diff --git a/save.c b/save.c new file mode 100644 index 0000000..3e8a656 --- /dev/null +++ b/save.c @@ -0,0 +1,111 @@ +#include +#include +#include "save.h" +#include "aes.h" + +void save_process(save_ctx_t *ctx) { + /* Read *just* safe amount. */ + fseeko64(ctx->file, 0, SEEK_SET); + if (fread(&ctx->header, 1, sizeof(ctx->header), ctx->file) != sizeof(ctx->header)) { + fprintf(stderr, "Failed to read save header!\n"); + exit(EXIT_FAILURE); + } + + if ((ctx->tool_ctx->action & ACTION_VERIFY)) { + ctx->hash_validity = check_memory_hash_table(ctx->file, ctx->header.layout.hash, 0x300, 0x3D00, 0x3D00, 0); + + unsigned char cmac[0x10]; + memset(cmac, 0, 0x10); + aes_calculate_cmac(cmac, &ctx->header.layout, sizeof(ctx->header.layout), ctx->tool_ctx->settings.keyset.save_mac_key); + if (memcmp(cmac, &ctx->header.cmac, 0x10) == 0) { + ctx->disf_cmac_validity = VALIDITY_VALID; + } else { + ctx->disf_cmac_validity = VALIDITY_INVALID; + } + } + + if (ctx->tool_ctx->action & ACTION_INFO) { + save_print(ctx); + } + + if (ctx->tool_ctx->action & ACTION_EXTRACT) { + save_save(ctx); + } +} + +void save_save(save_ctx_t *ctx) { + +} + +void save_print_ivfc_section(save_ctx_t *ctx) { + print_magic(" Magic: ", ctx->header.ivfc_header.magic); + printf(" ID: %08"PRIx32"\n", ctx->header.ivfc_header.id); + memdump(stdout, " Salt Seed: ", &ctx->header.ivfc_header.salt_source, 0x20); + for (unsigned int i = 0; i < 4; i++) { + printf(" Level %"PRId32":\n", i); + printf(" Data Offset: 0x%016"PRIx64"\n", ctx->header.ivfc_header.level_headers[i].logical_offset); + printf(" Data Size: 0x%016"PRIx64"\n", ctx->header.ivfc_header.level_headers[i].hash_data_size); + if (i != 0) { + printf(" Hash Offset: 0x%016"PRIx64"\n", ctx->header.ivfc_header.level_headers[i-1].logical_offset); + } else { + printf(" Hash Offset: 0x%016"PRIx64"\n", 0); + } + printf(" Hash Block Size: 0x%08"PRIx32"\n", 1 << ctx->header.ivfc_header.level_headers[i].block_size); + } +} + +static const char *save_get_save_type(save_ctx_t *ctx) { + switch (ctx->header.extra_data.save_data_type) { + case 0: + return "SystemSaveData"; + case 1: + return "SaveData"; + case 2: + return "BcatDeliveryCacheStorage"; + case 3: + return "DeviceSaveData"; + case 4: + return "TemporaryStorage"; + case 5: + return "CacheStorage"; + default: + return "Unknown"; + } +} + +void save_print(save_ctx_t *ctx) { + printf("\nSave:\n"); + + if (ctx->tool_ctx->action & ACTION_VERIFY) { + if (ctx->disf_cmac_validity == VALIDITY_VALID) { + memdump(stdout, "Header CMAC (GOOD): ", &ctx->header.cmac, 0x10); + } else { + memdump(stdout, "Header CMAC (FAIL): ", &ctx->header.cmac, 0x10); + } + } else { + memdump(stdout, "Header CMAC: ", &ctx->header.cmac, 0x10); + } + + printf("Title ID: %016"PRIx64"\n", ctx->header.extra_data.title_id); + memdump(stdout, "User ID: ", &ctx->header.extra_data.user_id, 0x10); + printf("Save ID: %016"PRIx64"\n", ctx->header.extra_data.save_id); + printf("Save Type: %s\n", save_get_save_type(ctx)); + printf("Owner ID: %016"PRIx64"\n", ctx->header.extra_data.save_owner_id); + char timestamp[70]; + if (strftime(timestamp, sizeof(timestamp), "%F %T UTC", gmtime((time_t *)&ctx->header.extra_data.timestamp))) + printf("Timestamp: %s\n", timestamp); + printf("Save Data Size: %016"PRIx64"\n", ctx->header.extra_data.data_size); + printf("Journal Size: %016"PRIx64"\n", ctx->header.extra_data.journal_size); + + if (ctx->tool_ctx->action & ACTION_VERIFY) { + if (ctx->hash_validity == VALIDITY_VALID) { + memdump(stdout, "Header Hash (GOOD): ", &ctx->header.layout.hash, 0x20); + } else { + memdump(stdout, "Header Hash (FAIL): ", &ctx->header.layout.hash, 0x20); + } + } else { + memdump(stdout, "Header Hash: ", &ctx->header.layout.hash, 0x20); + } + + save_print_ivfc_section(ctx); +} diff --git a/save.h b/save.h new file mode 100644 index 0000000..86b673b --- /dev/null +++ b/save.h @@ -0,0 +1,172 @@ +#ifndef HACTOOL_SAVE_H +#define HACTOOL_SAVE_H +#include "types.h" +#include "settings.h" +#include "ivfc.h" + +#define SAVE_HEADER_SIZE 0x4000 + +#define MAGIC_DISF 0x46534944 +#define MAGIC_DPFS 0x53465044 +#define MAGIC_JNGL 0x4C474E4A +#define MAGIC_SAVE 0x45564153 +#define MAGIC_RMAP 0x50414D52 +#define MAGIC_IVFC 0x43465649 + +typedef struct { + uint32_t magic; /* DISF */ + uint32_t version; + uint8_t hash[0x20]; + uint64_t file_map_entry_offset; + uint64_t file_map_entry_size; + uint64_t meta_map_entry_offset; + uint64_t meta_map_entry_size; + uint64_t file_map_data_offset; + uint64_t file_map_data_size; + uint64_t duplex_l1_offset_a; + uint64_t duplex_l1_offset_b; + uint64_t duplex_l1_size; + uint64_t duplex_data_offset_a; + uint64_t duplex_data_offset_b; + uint64_t duplex_data_size; + uint64_t journal_data_offset; + uint64_t journal_data_size_a; + uint64_t journal_data_size_b; + uint64_t journal_size; + uint64_t duplex_master_offset_a; + uint64_t duplex_master_offset_b; + uint64_t duplex_master_size; + uint64_t ivfc_master_hash_offset_a; + uint64_t ivfc_master_hash_offset_b; + uint64_t ivfc_master_hash_size; + uint64_t journal_map_table_offset; + uint64_t journal_map_table_size; + uint64_t journal_physical_bitmap_offset; + uint64_t journal_physical_bitmap_size; + uint64_t journal_virtual_bitmap_offset; + uint64_t journal_virtual_bitmap_size; + uint64_t journal_free_bitmap_offset; + uint64_t journal_free_bitmap_size; + uint64_t ivfc_l1_offset; + uint64_t ivfc_l1_size; + uint64_t ivfc_l2_offset; + uint64_t ivfc_l2_size; + uint64_t ivfc_l3_offset; + uint64_t ivfc_l3_size; + uint64_t fat_offset; + uint64_t fat_size; + uint64_t duplex_index; + uint64_t fat_ivfc_master_hash_a; + uint64_t fat_ivfc_master_hash_b; + uint64_t fat_ivfc_l1_offset; + uint64_t fat_ivfc_l1_size; + uint64_t fat_ivfc_l2_offset; + uint64_t fat_ivfc_l2_size; + uint8_t _0x190[0x70]; +} fs_layout_t; + +#pragma pack(push, 1) +typedef struct { + uint64_t offset; + uint64_t length; + uint32_t block_size_power; +} duplex_info_t; +#pragma pack(pop) + +typedef struct { + uint32_t magic; /* DPFS */ + uint32_t version; + duplex_info_t layers[3]; +} duplex_header_t; + +typedef struct { + uint32_t version; + uint32_t main_data_block_count; + uint32_t journal_block_count; + uint32_t _0x0C; +} journal_map_header_t; + +typedef struct { + uint32_t magic; /* JNGL */ + uint32_t version; + uint64_t total_size; + uint64_t journal_size; + uint64_t block_size; +} journal_header_t; + +typedef struct { + uint32_t magic; /* SAVE */ + uint32_t version; + uint64_t block_count; + uint64_t block_size; +} save_fs_header_t; + +typedef struct { + uint64_t block_size; + uint64_t fat_offset; + uint32_t fat_block_count; + uint32_t _0x14; + uint64_t data_offset; + uint32_t data_block_count; + uint32_t _0x24; + uint32_t directory_table_block; + uint32_t file_table_block; +} fat_header_t; + +typedef struct { + uint32_t magic; /* RMAP */ + uint32_t version; + uint32_t map_entry_count; + uint32_t map_segment_count; + uint32_t segment_bits; + uint8_t _0x14[0x2C]; +} remap_header_t; + +typedef struct { + uint64_t title_id; + uint8_t user_id[0x10]; + uint64_t save_id; + uint8_t save_data_type; + uint8_t _0x21[0x1F]; + uint64_t save_owner_id; + uint64_t timestamp; + uint64_t _0x50; + uint64_t data_size; + uint64_t journal_size; + uint64_t commit_id; +} extra_data_t; + +#pragma pack(push, 1) +typedef struct { + uint8_t cmac[0x10]; + uint8_t _0x10[0xF0]; + fs_layout_t layout; + duplex_header_t duplex_header; + ivfc_save_hdr_t ivfc_header; + uint32_t _0x404; + journal_header_t journal_header; + journal_map_header_t map_header; + uint8_t _0x438[0x1D0]; + save_fs_header_t save_header; + fat_header_t fat_header; + remap_header_t main_remap_header, meta_remap_header; + uint64_t _0x6D0; + extra_data_t extra_data; + uint8_t _0x748[0x390]; + ivfc_save_hdr_t fat_ivfc_header; +} save_header_t; +#pragma pack(pop) + +typedef struct { + FILE *file; + hactool_ctx_t *tool_ctx; + save_header_t header; + validity_t disf_cmac_validity; + validity_t hash_validity; +} save_ctx_t; + +void save_process(save_ctx_t *ctx); +void save_save(save_ctx_t *ctx); +void save_print(save_ctx_t *ctx); + +#endif