mirror of
https://github.com/SciresM/hactool
synced 2024-11-21 19:53:01 +00:00
Add preliminary savefile parsing support
This commit is contained in:
parent
b8062ab149
commit
ae795bba8b
3 changed files with 292 additions and 0 deletions
9
ivfc.h
9
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
|
||||
|
||||
|
|
111
save.c
Normal file
111
save.c
Normal file
|
@ -0,0 +1,111 @@
|
|||
#include <string.h>
|
||||
#include <time.h>
|
||||
#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);
|
||||
}
|
172
save.h
Normal file
172
save.h
Normal file
|
@ -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
|
Loading…
Reference in a new issue