Huge savedata driver refactor

This commit is contained in:
shchmue 2020-12-03 18:43:16 -07:00
parent bd134cf670
commit 04378b322d
44 changed files with 5485 additions and 1355 deletions

View File

@ -0,0 +1,281 @@
/*
* Copyright (c) 2019-2020 shchmue
*
* 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/>.
*/
/*
ISC License
hactool Copyright (c) 2018, SciresM
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "allocation_table.h"
#include "allocation_table_iterator.h"
#include <gfx_utils.h>
void save_allocation_table_init(allocation_table_ctx_t *ctx, void *storage, allocation_table_header_t *header) {
ctx->base_storage = storage;
ctx->header = header;
ctx->free_list_entry_index = 0;
}
void save_allocation_table_set_free_list_entry_index(allocation_table_ctx_t *ctx, uint32_t head_block_index) {
allocation_table_entry_t free_list = {0, head_block_index};
save_allocation_table_write_entry(ctx, ctx->free_list_entry_index, &free_list);
}
void save_allocation_table_set_free_list_block_index(allocation_table_ctx_t *ctx, uint32_t head_block_index) {
save_allocation_table_set_free_list_entry_index(ctx, allocation_table_block_to_entry_index(head_block_index));
}
uint32_t save_allocation_table_get_list_tail(allocation_table_ctx_t *ctx, uint32_t entry_index) {
uint32_t tail_index = entry_index;
uint32_t table_size = ctx->header->fat_storage_info.count;
uint32_t nodes_traversed = 0;
allocation_table_entry_t *entry = save_allocation_table_read_entry(ctx, entry_index);
while (!allocation_table_is_list_end(entry)) {
nodes_traversed++;
tail_index = allocation_table_get_next(entry);
entry = save_allocation_table_read_entry(ctx, tail_index);
if (nodes_traversed > table_size) {
EPRINTF("Cycle detected in allocation table!");
return 0xFFFFFFFF;
}
}
return tail_index;
}
bool save_allocation_table_split(allocation_table_ctx_t *ctx, uint32_t segment_block_index, uint32_t first_sub_segment_length) {
uint32_t seg_a_index = allocation_table_block_to_entry_index(segment_block_index);
allocation_table_entry_t *seg_a = save_allocation_table_read_entry(ctx, seg_a_index);
if (!allocation_table_is_multi_block_segment(seg_a)) {
EPRINTF("Cannot split a single-entry segment!");
return false;
}
allocation_table_entry_t *seg_a_range = save_allocation_table_read_entry(ctx, seg_a_index + 1);
uint32_t original_length = allocation_table_get_next(seg_a_range) - allocation_table_get_prev(seg_a_range) + 1;
if (first_sub_segment_length >= original_length) {
EPRINTFARGS("Requested sub-segment length (%x) must\n be less than the full segment length (%x)!", first_sub_segment_length, original_length);
return false;
}
uint32_t seg_b_index = seg_a_index + first_sub_segment_length;
uint32_t seg_a_length = first_sub_segment_length;
uint32_t seg_b_length = original_length - seg_a_length;
allocation_table_entry_t seg_b = {seg_a_index, allocation_table_get_next(seg_a)};
allocation_table_set_next(seg_a, seg_b_index);
if (!allocation_table_is_list_end(&seg_b)) {
allocation_table_entry_t *seg_c = save_allocation_table_read_entry(ctx, allocation_table_get_next(&seg_b));
allocation_table_set_prev(seg_c, seg_b_index);
}
if (seg_b_length > 1) {
allocation_table_make_multi_block_segment(&seg_b);
allocation_table_entry_t seg_b_range;
allocation_table_set_range(&seg_b_range, seg_b_index, seg_b_index + seg_b_length - 1);
save_allocation_table_write_entry(ctx, seg_b_index + 1, &seg_b_range);
save_allocation_table_write_entry(ctx, seg_b_index + seg_b_length - 1, &seg_b_range);
}
save_allocation_table_write_entry(ctx, seg_b_index, &seg_b);
if (seg_a_length == 1) {
allocation_table_make_single_block_segment(seg_a);
} else {
allocation_table_set_range(seg_a_range, seg_a_index, seg_a_index + seg_a_length - 1);
save_allocation_table_write_entry(ctx, seg_a_index + seg_a_length - 1, seg_a_range);
}
return true;
}
uint32_t save_allocation_table_trim(allocation_table_ctx_t *ctx, uint32_t list_head_block_index, uint32_t new_list_length) {
uint32_t blocks_remaining = new_list_length;
uint32_t next_entry = allocation_table_block_to_entry_index(list_head_block_index);
uint32_t list_a_index = 0xFFFFFFFF;
uint32_t list_b_index = 0xFFFFFFFF;
while (blocks_remaining > 0) {
if (next_entry == 0)
return 0xFFFFFFFF;
uint32_t current_entry_index = next_entry;
allocation_table_entry_t entry;
uint32_t segment_length = save_allocation_table_read_entry_with_length(ctx, allocation_table_entry_index_to_block(current_entry_index), &entry);
if (segment_length == 0) {
EPRINTF("Invalid entry detected in allocation table!");
return 0xFFFFFFFF;
}
next_entry = allocation_table_block_to_entry_index(entry.next);
if (segment_length == blocks_remaining) {
list_a_index = current_entry_index;
list_b_index = next_entry;
} else if (segment_length > blocks_remaining) {
if (!save_allocation_table_split(ctx, allocation_table_entry_index_to_block(current_entry_index), blocks_remaining))
return 0xFFFFFFFF;
list_a_index = current_entry_index;
list_b_index = current_entry_index + blocks_remaining;
segment_length = blocks_remaining;
}
blocks_remaining -= segment_length;
}
if (list_a_index == 0xFFFFFFFF || list_b_index == 0xFFFFFFFF)
return 0xFFFFFFFF;
allocation_table_entry_t *list_a_node = save_allocation_table_read_entry(ctx, list_a_index);
allocation_table_entry_t *list_b_node = save_allocation_table_read_entry(ctx, list_b_index);
allocation_table_set_next(list_a_node, 0);
allocation_table_make_list_start(list_b_node);
return allocation_table_entry_index_to_block(list_b_index);
}
bool save_allocation_table_join(allocation_table_ctx_t *ctx, uint32_t front_list_block_index, uint32_t back_list_block_index) {
uint32_t front_entry_index = allocation_table_block_to_entry_index(front_list_block_index);
uint32_t back_entry_index = allocation_table_block_to_entry_index(back_list_block_index);
uint32_t front_tail_index = save_allocation_table_get_list_tail(ctx, front_entry_index);
if (front_tail_index == 0xFFFFFFFF)
return false;
allocation_table_entry_t *front_tail = save_allocation_table_read_entry(ctx, front_tail_index);
allocation_table_entry_t *back_head = save_allocation_table_read_entry(ctx, back_entry_index);
allocation_table_set_next(front_tail, back_entry_index);
allocation_table_set_prev(back_head, front_tail_index);
return true;
}
uint32_t save_allocation_table_allocate(allocation_table_ctx_t *ctx, uint32_t block_count) {
uint32_t free_list = save_allocation_table_get_free_list_block_index(ctx);
uint32_t new_free_list = save_allocation_table_trim(ctx, free_list, block_count);
if (new_free_list == 0xFFFFFFFF)
return 0xFFFFFFFF;
save_allocation_table_set_free_list_block_index(ctx, new_free_list);
return free_list;
}
bool save_allocation_table_free(allocation_table_ctx_t *ctx, uint32_t list_block_index) {
uint32_t list_entry_index = allocation_table_block_to_entry_index(list_block_index);
allocation_table_entry_t *list_entry = save_allocation_table_read_entry(ctx, list_entry_index);
if (!allocation_table_is_list_start(list_entry)) {
EPRINTF("The block to free must be the start of a list!");
return false;
}
uint32_t free_list_index = save_allocation_table_get_free_list_entry_index(ctx);
if (free_list_index == 0) {
save_allocation_table_set_free_list_entry_index(ctx, list_entry_index);
return true;
}
save_allocation_table_join(ctx, list_block_index, allocation_table_entry_index_to_block(free_list_index));
save_allocation_table_set_free_list_block_index(ctx, list_block_index);
return true;
}
uint32_t save_allocation_table_read_entry_with_length(allocation_table_ctx_t *ctx, uint32_t block_index, allocation_table_entry_t *entry) {
uint32_t length;
uint32_t entry_index = allocation_table_block_to_entry_index(block_index);
allocation_table_entry_t *entries = save_allocation_table_read_entry(ctx, entry_index);
if (allocation_table_is_single_block_segment(&entries[0])) {
length = 1;
if (allocation_table_is_range_entry(&entries[0])) {
EPRINTF("Invalid range entry in allocation table!");
return 0;
}
} else {
length = entries[1].next - entry_index + 1;
}
if (allocation_table_is_list_end(&entries[0])) {
entry->next = 0xFFFFFFFF;
} else {
entry->next = allocation_table_entry_index_to_block(allocation_table_get_next(&entries[0]));
}
if (allocation_table_is_list_start(&entries[0])) {
entry->prev = 0xFFFFFFFF;
} else {
entry->prev = allocation_table_entry_index_to_block(allocation_table_get_prev(&entries[0]));
}
return length;
}
uint32_t save_allocation_table_get_list_length(allocation_table_ctx_t *ctx, uint32_t block_index) {
allocation_table_entry_t entry = {0, block_index};
uint32_t total_length = 0;
uint32_t table_size = ctx->header->fat_storage_info.count;
uint32_t nodes_iterated = 0;
while (entry.next != 0xFFFFFFFF) {
uint32_t length = save_allocation_table_read_entry_with_length(ctx, entry.next, &entry);
if (length == 0) {
EPRINTF("Invalid entry detected in allocation table!");
return 0;
}
total_length += length;
nodes_iterated++;
if (nodes_iterated > table_size) {
EPRINTF("Cycle detected in allocation table!");
return 0;
}
}
return total_length;
}
uint32_t save_allocation_table_get_free_list_length(allocation_table_ctx_t *ctx) {
uint32_t free_list_start = save_allocation_table_get_free_list_block_index(ctx);
if (free_list_start == 0xFFFFFFFF)
return 0;
return save_allocation_table_get_list_length(ctx, free_list_start);
}

View File

@ -0,0 +1,171 @@
/*
* Copyright (c) 2019-2020 shchmue
*
* 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/>.
*/
/*
ISC License
hactool Copyright (c) 2018, SciresM
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef _ALLOCATION_TABLE_H_
#define _ALLOCATION_TABLE_H_
#include "storage.h"
#include <assert.h>
#include <stdint.h>
#include <string.h>
#define SAVE_FAT_ENTRY_SIZE 8
typedef struct {
uint64_t offset;
uint32_t count;
uint32_t _0xC;
} storage_info_t;
typedef struct {
uint64_t block_size;
storage_info_t fat_storage_info;
storage_info_t data_storage_info;
uint32_t directory_table_block;
uint32_t file_table_block;
} allocation_table_header_t;
static_assert(sizeof(allocation_table_header_t) == 0x30, "Allocation table header size is wrong!");
typedef struct {
uint32_t prev;
uint32_t next;
} allocation_table_entry_t;
typedef struct {
uint32_t free_list_entry_index;
void *base_storage;
allocation_table_header_t *header;
} allocation_table_ctx_t;
static ALWAYS_INLINE uint32_t allocation_table_entry_index_to_block(uint32_t entry_index) {
return entry_index - 1;
}
static ALWAYS_INLINE uint32_t allocation_table_block_to_entry_index(uint32_t block_index) {
return block_index + 1;
}
static ALWAYS_INLINE int allocation_table_get_prev(allocation_table_entry_t *entry) {
return entry->prev & 0x7FFFFFFF;
}
static ALWAYS_INLINE int allocation_table_get_next(allocation_table_entry_t *entry) {
return entry->next & 0x7FFFFFFF;
}
static ALWAYS_INLINE int allocation_table_is_list_start(allocation_table_entry_t *entry) {
return entry->prev == 0x80000000;
}
static ALWAYS_INLINE int allocation_table_is_list_end(allocation_table_entry_t *entry) {
return (entry->next & 0x7FFFFFFF) == 0;
}
static ALWAYS_INLINE bool allocation_table_is_multi_block_segment(allocation_table_entry_t *entry) {
return entry->next & 0x80000000;
}
static ALWAYS_INLINE void allocation_table_make_multi_block_segment(allocation_table_entry_t *entry) {
entry->next |= 0x80000000;
}
static ALWAYS_INLINE void allocation_table_make_single_block_segment(allocation_table_entry_t *entry) {
entry->next &= 0x7FFFFFFF;
}
static ALWAYS_INLINE bool allocation_table_is_single_block_segment(allocation_table_entry_t *entry) {
return (entry->next & 0x80000000) == 0;
}
static ALWAYS_INLINE void allocation_table_make_list_start(allocation_table_entry_t *entry) {
entry->prev = 0x80000000;
}
static ALWAYS_INLINE bool allocation_table_is_range_entry(allocation_table_entry_t *entry) {
return (entry->prev & 0x80000000) == 0x80000000 && entry->prev != 0x80000000;
}
static ALWAYS_INLINE void allocation_table_make_range_entry(allocation_table_entry_t *entry) {
entry->prev |= 0x80000000;
}
static ALWAYS_INLINE void allocation_table_set_next(allocation_table_entry_t *entry, int val) {
entry->next = (entry->next & 0x80000000) | val;
}
static ALWAYS_INLINE void allocation_table_set_prev(allocation_table_entry_t *entry, int val) {
entry->prev = val;
}
static ALWAYS_INLINE void allocation_table_set_range(allocation_table_entry_t *entry, int start_index, int end_index) {
entry->next = end_index;
entry->prev = start_index;
allocation_table_make_range_entry(entry);
}
static ALWAYS_INLINE uint64_t allocation_table_query_size(uint32_t block_count) {
return SAVE_FAT_ENTRY_SIZE * allocation_table_block_to_entry_index(block_count);
}
static ALWAYS_INLINE allocation_table_entry_t *save_allocation_table_read_entry(allocation_table_ctx_t *ctx, uint32_t entry_index) {
return (allocation_table_entry_t *)((uint8_t *)ctx->base_storage + entry_index * SAVE_FAT_ENTRY_SIZE);
}
static ALWAYS_INLINE void save_allocation_table_write_entry(allocation_table_ctx_t *ctx, uint32_t entry_index, allocation_table_entry_t *entry) {
memcpy((uint8_t *)ctx->base_storage + entry_index * SAVE_FAT_ENTRY_SIZE, entry, SAVE_FAT_ENTRY_SIZE);
}
static ALWAYS_INLINE uint32_t save_allocation_table_get_free_list_entry_index(allocation_table_ctx_t *ctx) {
return allocation_table_get_next(save_allocation_table_read_entry(ctx, ctx->free_list_entry_index));
}
static ALWAYS_INLINE uint32_t save_allocation_table_get_free_list_block_index(allocation_table_ctx_t *ctx) {
return allocation_table_entry_index_to_block(save_allocation_table_get_free_list_entry_index(ctx));
}
void save_allocation_table_init(allocation_table_ctx_t *ctx, void *storage, allocation_table_header_t *header);
void save_allocation_table_set_free_list_entry_index(allocation_table_ctx_t *ctx, uint32_t head_block_index);
void save_allocation_table_set_free_list_block_index(allocation_table_ctx_t *ctx, uint32_t head_block_index);
bool save_allocation_table_split(allocation_table_ctx_t *ctx, uint32_t segment_block_index, uint32_t first_sub_segment_length);
uint32_t save_allocation_table_trim(allocation_table_ctx_t *ctx, uint32_t list_head_block_index, uint32_t new_list_length);
bool save_allocation_table_join(allocation_table_ctx_t *ctx, uint32_t front_list_block_index, uint32_t back_list_block_index);
uint32_t save_allocation_table_allocate(allocation_table_ctx_t *ctx, uint32_t block_count);
bool save_allocation_table_free(allocation_table_ctx_t *ctx, uint32_t list_block_index);
uint32_t save_allocation_table_read_entry_with_length(allocation_table_ctx_t *ctx, uint32_t block_index, allocation_table_entry_t *entry);
uint32_t save_allocation_table_get_list_length(allocation_table_ctx_t *ctx, uint32_t block_index);
uint32_t save_allocation_table_get_free_list_length(allocation_table_ctx_t *ctx);
#endif

View File

@ -0,0 +1,111 @@
/*
* Copyright (c) 2019-2020 shchmue
*
* 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/>.
*/
/*
ISC License
hactool Copyright (c) 2018, SciresM
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "allocation_table_iterator.h"
#include <gfx_utils.h>
bool save_allocation_table_iterator_begin(allocation_table_iterator_ctx_t *ctx, allocation_table_ctx_t *table, uint32_t initial_block) {
ctx->fat = table;
ctx->physical_block = initial_block;
ctx->virtual_block = 0;
allocation_table_entry_t entry;
ctx->current_segment_size = save_allocation_table_read_entry_with_length(ctx->fat, initial_block, &entry);
if (ctx->current_segment_size == 0) {
EPRINTF("Invalid entry detected in allocation table!");
return false;
}
ctx->next_block = entry.next;
ctx->prev_block = entry.prev;
if (ctx->prev_block != 0xFFFFFFFF) {
EPRINTFARGS("Attempted to start FAT iteration from\n invalid block %x!", initial_block);
return false;
}
return true;
}
bool save_allocation_table_iterator_move_next(allocation_table_iterator_ctx_t *ctx) {
if (ctx->next_block == 0xFFFFFFFF)
return false;
ctx->virtual_block += ctx->current_segment_size;
ctx->physical_block = ctx->next_block;
allocation_table_entry_t entry;
ctx->current_segment_size = save_allocation_table_read_entry_with_length(ctx->fat, ctx->next_block, &entry);
if (ctx->current_segment_size == 0) {
EPRINTF("Invalid entry detected in allocation table!");
return false;
}
ctx->next_block = entry.next;
ctx->prev_block = entry.prev;
return true;
}
bool save_allocation_table_iterator_move_prev(allocation_table_iterator_ctx_t *ctx) {
if (ctx->prev_block == 0xFFFFFFFF)
return false;
ctx->physical_block = ctx->prev_block;
allocation_table_entry_t entry;
ctx->current_segment_size = save_allocation_table_read_entry_with_length(ctx->fat, ctx->prev_block, &entry);
if (ctx->current_segment_size == 0) {
EPRINTF("Invalid entry detected in allocation table!");
return false;
}
ctx->next_block = entry.next;
ctx->prev_block = entry.prev;
ctx->virtual_block -= ctx->current_segment_size;
return true;
}
bool save_allocation_table_iterator_seek(allocation_table_iterator_ctx_t *ctx, uint32_t block) {
for ( ; ; ) {
if (block < ctx->virtual_block) {
if (!save_allocation_table_iterator_move_prev(ctx))
return false;
} else if (block >= ctx->virtual_block + ctx->current_segment_size) {
if (!save_allocation_table_iterator_move_next(ctx))
return false;
} else {
return true;
}
}
}

View File

@ -0,0 +1,56 @@
/*
* Copyright (c) 2019-2020 shchmue
*
* 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/>.
*/
/*
ISC License
hactool Copyright (c) 2018, SciresM
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef _ALLOCATION_TABLE_ITER_H_
#define _ALLOCATION_TABLE_ITER_H_
#include "allocation_table.h"
#include <stdint.h>
typedef struct {
allocation_table_ctx_t *fat;
uint32_t virtual_block;
uint32_t physical_block;
uint32_t current_segment_size;
uint32_t next_block;
uint32_t prev_block;
} allocation_table_iterator_ctx_t;
bool save_allocation_table_iterator_begin(allocation_table_iterator_ctx_t *ctx, allocation_table_ctx_t *table, uint32_t initial_block);
bool save_allocation_table_iterator_move_next(allocation_table_iterator_ctx_t *ctx);
bool save_allocation_table_iterator_move_prev(allocation_table_iterator_ctx_t *ctx);
bool save_allocation_table_iterator_seek(allocation_table_iterator_ctx_t *ctx, uint32_t block);
#endif

View File

@ -0,0 +1,158 @@
/*
* Copyright (c) 2019-2020 shchmue
*
* 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/>.
*/
/*
ISC License
hactool Copyright (c) 2018, SciresM
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "allocation_table_storage.h"
#include "allocation_table_iterator.h"
#include <gfx_utils.h>
void save_allocation_table_storage_init(allocation_table_storage_ctx_t *ctx, substorage *data, allocation_table_ctx_t *table, uint32_t block_size, uint32_t initial_block) {
ctx->base_storage = data;
ctx->block_size = block_size;
ctx->fat = table;
ctx->initial_block = initial_block;
ctx->_length = initial_block == 0xFFFFFFFF ? 0 : save_allocation_table_get_list_length(table, initial_block) * block_size;
}
uint32_t save_allocation_table_storage_read(allocation_table_storage_ctx_t *ctx, void *buffer, uint64_t offset, uint64_t count) {
allocation_table_iterator_ctx_t iterator;
if (!save_allocation_table_iterator_begin(&iterator, ctx->fat, ctx->initial_block))
return 0;
uint64_t in_pos = offset;
uint32_t out_pos = 0;
uint32_t remaining = count;
while (remaining) {
uint32_t block_num = (uint32_t)(in_pos / ctx->block_size);
if (!save_allocation_table_iterator_seek(&iterator, block_num)) {
EPRINTFARGS("Invalid allocation table offset: %x", (uint32_t)offset);
return 0;
}
uint32_t segment_pos = (uint32_t)(in_pos - (uint64_t)iterator.virtual_block * ctx->block_size);
uint64_t physical_offset = iterator.physical_block * ctx->block_size + segment_pos;
uint32_t remaining_in_segment = iterator.current_segment_size * ctx->block_size - segment_pos;
uint32_t bytes_to_read = MIN(remaining, remaining_in_segment);
if (substorage_read(ctx->base_storage, (uint8_t *)buffer + out_pos, physical_offset, bytes_to_read) != bytes_to_read)
return 0;
out_pos += bytes_to_read;
in_pos += bytes_to_read;
remaining -= bytes_to_read;
}
return out_pos;
}
uint32_t save_allocation_table_storage_write(allocation_table_storage_ctx_t *ctx, const void *buffer, uint64_t offset, uint64_t count) {
allocation_table_iterator_ctx_t iterator;
if (!save_allocation_table_iterator_begin(&iterator, ctx->fat, ctx->initial_block))
return 0;
uint64_t in_pos = offset;
uint32_t out_pos = 0;
uint32_t remaining = count;
while (remaining) {
uint32_t block_num = (uint32_t)(in_pos / ctx->block_size);
if (!save_allocation_table_iterator_seek(&iterator, block_num)) {
EPRINTFARGS("Invalid allocation table offset: %x", (uint32_t)offset);
return 0;
}
uint32_t segment_pos = (uint32_t)(in_pos - (uint64_t)iterator.virtual_block * ctx->block_size);
uint64_t physical_offset = iterator.physical_block * ctx->block_size + segment_pos;
uint32_t remaining_in_segment = iterator.current_segment_size * ctx->block_size - segment_pos;
uint32_t bytes_to_write = MIN(remaining, remaining_in_segment);
if (substorage_write(ctx->base_storage, (uint8_t *)buffer + out_pos, physical_offset, bytes_to_write) != bytes_to_write)
return 0;
out_pos += bytes_to_write;
in_pos += bytes_to_write;
remaining -= bytes_to_write;
}
return out_pos;
}
bool save_allocation_table_storage_set_size(allocation_table_storage_ctx_t *ctx, uint64_t size) {
uint32_t old_block_count = (uint32_t)DIV_ROUND_UP(ctx->_length, ctx->block_size);
uint32_t new_block_count = (uint32_t)DIV_ROUND_UP(size, ctx->block_size);
if (old_block_count == new_block_count)
return true;
if (old_block_count == 0) {
ctx->initial_block = save_allocation_table_allocate(ctx->fat, new_block_count);
if (ctx->initial_block == 0xFFFFFFFF) {
EPRINTF("Not enough space to resize file!");
return false;
}
ctx->_length = new_block_count * ctx->block_size;
return true;
}
if (new_block_count == 0) {
save_allocation_table_free(ctx->fat, ctx->initial_block);
ctx->initial_block = 0x80000000;
ctx->_length = 0;
return true;
}
if (new_block_count > old_block_count) {
uint32_t new_blocks = save_allocation_table_allocate(ctx->fat, new_block_count - old_block_count);
if (new_blocks == 0xFFFFFFFF) {
EPRINTF("Not enough space to resize file!");
return false;
}
if (!save_allocation_table_join(ctx->fat, ctx->initial_block, new_blocks))
return false;
} else {
uint32_t old_blocks = save_allocation_table_trim(ctx->fat, ctx->initial_block, new_block_count);
if (old_blocks == 0xFFFFFFFF) {
EPRINTF("Failure to trim!");
return false;
}
if (!save_allocation_table_free(ctx->fat, old_blocks))
return false;
}
ctx->_length = new_block_count * ctx->block_size;
return true;
}

View File

@ -0,0 +1,60 @@
/*
* Copyright (c) 2019-2020 shchmue
*
* 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/>.
*/
/*
ISC License
hactool Copyright (c) 2018, SciresM
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef _ALLOCATION_TABLE_STORAGE_H_
#define _ALLOCATION_TABLE_STORAGE_H_
#include "allocation_table.h"
#include "storage.h"
#include <stdint.h>
typedef struct {
substorage *base_storage;
uint32_t block_size;
uint32_t initial_block;
allocation_table_ctx_t *fat;
uint64_t _length;
} allocation_table_storage_ctx_t;
static ALWAYS_INLINE void save_allocation_table_storage_get_size(allocation_table_storage_ctx_t *ctx, uint64_t *out_size) {
*out_size = ctx->_length;
}
void save_allocation_table_storage_init(allocation_table_storage_ctx_t *ctx, substorage *data, allocation_table_ctx_t *table, uint32_t block_size, uint32_t initial_block);
uint32_t save_allocation_table_storage_read(allocation_table_storage_ctx_t *ctx, void *buffer, uint64_t offset, uint64_t count);
uint32_t save_allocation_table_storage_write(allocation_table_storage_ctx_t *ctx, const void *buffer, uint64_t offset, uint64_t count);
bool save_allocation_table_storage_set_size(allocation_table_storage_ctx_t *ctx, uint64_t size);
#endif

View File

@ -0,0 +1,227 @@
/*
* Copyright (c) 2019-2020 shchmue
*
* 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/>.
*/
/*
ISC License
hactool Copyright (c) 2018, SciresM
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "cached_storage.h"
#include <gfx_utils.h>
#include <mem/heap.h>
#include <string.h>
static ALWAYS_INLINE cache_block_t *cache_block_init(cached_storage_ctx_t *ctx) {
cache_block_t *block = calloc(1, sizeof(cache_block_t));
block->buffer = malloc(ctx->block_size);
block->index = -1;
return block;
}
void save_cached_storage_init(cached_storage_ctx_t *ctx, substorage *base_storage, uint32_t block_size, uint32_t cache_size) {
memcpy(&ctx->base_storage, base_storage, sizeof(substorage));
ctx->block_size = block_size;
substorage_get_size(base_storage, &ctx->length);
ctx->cache_size = cache_size;
list_init(&ctx->blocks);
for (uint32_t i = 0; i < cache_size; i++) {
cache_block_t *block = cache_block_init(ctx);
list_append(&ctx->blocks, &block->link);
}
}
void save_cached_storage_init_from_sector_storage(cached_storage_ctx_t *ctx, sector_storage *base_storage, uint32_t cache_size) {
save_cached_storage_init(ctx, &base_storage->base_storage, base_storage->sector_size, cache_size);
}
static void cache_block_finalize(cache_block_t **block) {
free((*block)->buffer);
free(*block);
}
void save_cached_storage_finalize(cached_storage_ctx_t *ctx) {
LIST_FOREACH_SAFE(curr_block, &ctx->blocks) {
cache_block_t *block = CONTAINER_OF(curr_block, cache_block_t, link) ;
cache_block_finalize(&block);
}
}
static bool try_get_block_by_value(cached_storage_ctx_t *ctx, uint64_t index, cache_block_t **out_block) {
LIST_FOREACH_ENTRY(cache_block_t, block, &ctx->blocks, link) {
if (block->index == index) {
*out_block = block;
return true;
}
}
return false;
}
static bool flush_block(cached_storage_ctx_t *ctx, cache_block_t *block) {
if (!block->dirty)
return true;
uint64_t offset = block->index * ctx->block_size;
if (substorage_write(&ctx->base_storage, block->buffer, offset, block->length) != block->length) {
EPRINTF("Cached storage: Failed to write block!");
return false;
}
block->dirty = false;
return true;
}
static bool read_block(cached_storage_ctx_t *ctx, cache_block_t *block, uint64_t index) {
uint64_t offset = index * ctx->block_size;
uint32_t length = ctx->block_size;
if (ctx->length != -1)
length = (uint32_t)MIN(ctx->length - offset, length);
if (substorage_read(&ctx->base_storage, block->buffer, offset, length) != length) {
EPRINTF("Cached storage: Failed to read block!");
return false;
}
block->length = length;
block->index = index;
block->dirty = false;
return true;
}
static cache_block_t *get_block(cached_storage_ctx_t *ctx, uint64_t block_index) {
cache_block_t *block = NULL;
if (try_get_block_by_value(ctx, block_index, &block)) {
// Promote most recently used block to front of list if not already.
if (ctx->blocks.next != &block->link) {
list_remove(&block->link);
list_prepend(&ctx->blocks, &block->link);
}
return block;
}
// Get a pointer either to the least recently used block or to a newly allocated block if storage is empty.
bool block_is_new = false;
if (ctx->blocks.prev != &ctx->blocks) {
block = CONTAINER_OF(ctx->blocks.prev, cache_block_t, link);
if (!flush_block(ctx, block))
return NULL;
// Remove least recently used block from list.
list_remove(&block->link);
} else {
block = cache_block_init(ctx);
block_is_new = true;
}
if (!read_block(ctx, block, block_index)) {
if (block_is_new)
cache_block_finalize(&block);
return NULL;
}
list_prepend(&ctx->blocks, &block->link);
return block;
}
uint32_t save_cached_storage_read(cached_storage_ctx_t *ctx, void *buffer, uint64_t offset, uint64_t count) {
uint64_t remaining = count;
uint64_t in_offset = offset;
uint32_t out_offset = 0;
if (!is_range_valid(offset, count, ctx->length)) {
EPRINTF("Cached storage read out of range!");
return 0;
}
while (remaining) {
uint64_t block_index = in_offset / ctx->block_size;
uint32_t block_pos = (uint32_t)(in_offset % ctx->block_size);
cache_block_t *block = get_block(ctx,block_index);
if (!block) {
EPRINTFARGS("Cached storage read: Unable to get block\n at index %x", (uint32_t)block_index);
return 0;
}
uint32_t bytes_to_read = (uint32_t)MIN(remaining, ctx->block_size - block_pos);
memcpy((uint8_t *)buffer + out_offset, block->buffer + block_pos, bytes_to_read);
out_offset += bytes_to_read;
in_offset += bytes_to_read;
remaining -= bytes_to_read;
}
return out_offset;
}
uint32_t save_cached_storage_write(cached_storage_ctx_t *ctx, const void *buffer, uint64_t offset, uint64_t count) {
uint64_t remaining = count;
uint64_t in_offset = offset;
uint32_t out_offset = 0;
if (!is_range_valid(offset, count, ctx->length)) {
EPRINTF("Cached storage write out of range!");
return 0;
}
while (remaining) {
uint64_t block_index = in_offset / ctx->block_size;
uint32_t block_pos = (uint32_t)(in_offset % ctx->block_size);
cache_block_t *block = get_block(ctx,block_index);
if (!block) {
EPRINTFARGS("Cached storage write: Unable to get block\n at index %x", (uint32_t)block_index);
return 0;
}
uint32_t bytes_to_write = (uint32_t)MIN(remaining, ctx->block_size - block_pos);
memcpy(block->buffer + block_pos, (uint8_t *)buffer + out_offset, bytes_to_write);
block->dirty = true;
out_offset += bytes_to_write;
in_offset += bytes_to_write;
remaining -= bytes_to_write;
}
return out_offset;
}
bool save_cached_storage_flush(cached_storage_ctx_t *ctx) {
LIST_FOREACH_ENTRY(cache_block_t, block, &ctx->blocks, link) {
if (!flush_block(ctx, block))
return false;
}
return true;
}

View File

@ -0,0 +1,68 @@
/*
* Copyright (c) 2019-2020 shchmue
*
* 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/>.
*/
/*
ISC License
hactool Copyright (c) 2018, SciresM
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef _CACHED_STORAGE_H_
#define _CACHED_STORAGE_H_
#include "storage.h"
#include <utils/list.h>
#include <utils/types.h>
#include <stdint.h>
typedef struct {
uint64_t index;
uint8_t *buffer;
uint32_t length;
bool dirty;
link_t link;
} cache_block_t;
typedef struct {
substorage base_storage;
uint32_t block_size;
uint64_t length;
uint32_t cache_size;
link_t blocks;
} cached_storage_ctx_t;
void save_cached_storage_init(cached_storage_ctx_t *ctx, substorage *base_storage, uint32_t block_size, uint32_t cache_size);
void save_cached_storage_init_from_sector_storage(cached_storage_ctx_t *ctx, sector_storage *base_storage, uint32_t cache_size);
void save_cached_storage_finalize(cached_storage_ctx_t *ctx);
uint32_t save_cached_storage_read(cached_storage_ctx_t *ctx, void *buffer, uint64_t offset, uint64_t count);
uint32_t save_cached_storage_write(cached_storage_ctx_t *ctx, const void *buffer, uint64_t offset, uint64_t count);
bool save_cached_storage_flush(cached_storage_ctx_t *ctx);
#endif

View File

@ -0,0 +1,60 @@
/*
* Copyright (c) 2019-2020 shchmue
*
* 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/>.
*/
/*
ISC License
hactool Copyright (c) 2018, SciresM
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef _DIRECTORY_ENTRY_H_
#define _DIRECTORY_ENTRY_H_
#include <stdint.h>
typedef enum {
OPEN_DIR_MODE_DIR = 1,
OPEN_DIR_MODE_FILE = 2,
OPEN_DIR_MODE_NO_FILE_SIZE = -2147483648,
OPEN_DIR_MODE_ALL = OPEN_DIR_MODE_DIR | OPEN_DIR_MODE_FILE
} open_directory_mode_t;
typedef enum {
DIR_ENT_TYPE_DIR = 0,
DIR_ENT_TYPE_FILE
} directory_entry_type_t;
typedef struct {
char name[0x301];
uint8_t attributes;
uint8_t _0x302[2];
directory_entry_type_t type;
uint64_t size;
} directory_entry_t;
#endif

View File

@ -0,0 +1,108 @@
/*
* Copyright (c) 2019-2020 shchmue
*
* 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/>.
*/
/*
ISC License
hactool Copyright (c) 2018, SciresM
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "duplex_storage.h"
#include <gfx_utils.h>
#include <mem/heap.h>
void save_duplex_storage_init(duplex_storage_ctx_t *ctx, uint8_t *data_a, uint8_t *data_b, uint32_t block_size_power, void *bitmap, uint64_t bitmap_size) {
substorage_init(&ctx->data_a, &memory_storage_vt, data_a, 0, ctx->_length);
substorage_init(&ctx->data_b, &memory_storage_vt, data_b, 0, ctx->_length);
substorage_init(&ctx->bitmap_storage, &memory_storage_vt, bitmap, 0, bitmap_size);
ctx->block_size = 1 << block_size_power;
ctx->bitmap.data = (uint8_t *)bitmap;
ctx->bitmap.bitmap = malloc(bitmap_size >> 3);
uint32_t bits_remaining = (uint32_t)bitmap_size;
uint32_t bitmap_pos = 0;
uint32_t *buffer_pos = (uint32_t *)ctx->bitmap.data;
while (bits_remaining) {
uint32_t bits_to_read = MIN(bits_remaining, 0x20);
uint32_t val = *buffer_pos;
for (uint32_t i = 0; i < bits_to_read; i++) {
if (val & 0x80000000)
save_bitmap_set_bit(ctx->bitmap.bitmap, bitmap_pos);
else
save_bitmap_clear_bit(ctx->bitmap.bitmap, bitmap_pos);
bitmap_pos++;
bits_remaining--;
val <<= 1;
}
buffer_pos++;
}
}
uint32_t save_duplex_storage_read(duplex_storage_ctx_t *ctx, void *buffer, uint64_t offset, uint64_t count) {
uint64_t in_pos = offset;
uint32_t out_pos = 0;
uint32_t remaining = count;
while (remaining) {
uint32_t block_num = (uint32_t)(in_pos / ctx->block_size);
uint32_t block_pos = (uint32_t)(in_pos % ctx->block_size);
uint32_t bytes_to_read = MIN(ctx->block_size - block_pos, remaining);
substorage *data = save_bitmap_check_bit(ctx->bitmap.bitmap, block_num) ? &ctx->data_b : &ctx->data_a;
if (substorage_read(data, (uint8_t *)buffer + out_pos, in_pos, bytes_to_read) != bytes_to_read)
return 0;
out_pos += bytes_to_read;
in_pos += bytes_to_read;
remaining -= bytes_to_read;
}
return out_pos;
}
uint32_t save_duplex_storage_write(duplex_storage_ctx_t *ctx, const void *buffer, uint64_t offset, uint64_t count) {
uint64_t in_pos = offset;
uint32_t out_pos = 0;
uint32_t remaining = count;
while (remaining) {
uint32_t block_num = (uint32_t)(in_pos / ctx->block_size);
uint32_t block_pos = (uint32_t)(in_pos % ctx->block_size);
uint32_t bytes_to_write = MIN(ctx->block_size - block_pos, remaining);
substorage *data = save_bitmap_check_bit(ctx->bitmap.bitmap, block_num) ? &ctx->data_b : &ctx->data_a;
if (substorage_write(data, (uint8_t *)buffer + out_pos, in_pos, bytes_to_write) != bytes_to_write)
return 0;
out_pos += bytes_to_write;
in_pos += bytes_to_write;
remaining -= bytes_to_write;
}
return out_pos;
}

View File

@ -0,0 +1,76 @@
/*
* Copyright (c) 2019-2020 shchmue
*
* 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/>.
*/
/*
ISC License
hactool Copyright (c) 2018, SciresM
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef _DUPLEX_STORAGE_H_
#define _DUPLEX_STORAGE_H_
#include "storage.h"
#include <stdint.h>
#define DUPLEX_BITMAP_SIZE_BITS 0x200
#define DUPLEX_BITMAP_SIZE_BYTES 0x40
typedef struct {
uint8_t *data;
uint8_t *bitmap;
} duplex_bitmap_t;
typedef struct {
uint32_t block_size;
substorage bitmap_storage;
substorage data_a;
substorage data_b;
duplex_bitmap_t bitmap;
uint64_t _length;
substorage base_storage;
} duplex_storage_ctx_t;
static ALWAYS_INLINE void save_bitmap_set_bit(void *buffer, uint64_t bit_offset) {
*((uint8_t *)buffer + (bit_offset >> 3)) |= 1 << (bit_offset & 7);
}
static ALWAYS_INLINE void save_bitmap_clear_bit(void *buffer, uint64_t bit_offset) {
*((uint8_t *)buffer + (bit_offset >> 3)) &= ~(uint8_t)(1 << (bit_offset & 7));
}
static ALWAYS_INLINE uint8_t save_bitmap_check_bit(const void *buffer, uint64_t bit_offset) {
return *((uint8_t *)buffer + (bit_offset >> 3)) & (1 << (bit_offset & 7));
}
void save_duplex_storage_init(duplex_storage_ctx_t *ctx, uint8_t *data_a, uint8_t *data_b, uint32_t block_size_power, void *bitmap, uint64_t bitmap_size);
uint32_t save_duplex_storage_read(duplex_storage_ctx_t *ctx, void *buffer, uint64_t offset, uint64_t count);
uint32_t save_duplex_storage_write(duplex_storage_ctx_t *ctx, const void *buffer, uint64_t offset, uint64_t count);
#endif

View File

@ -0,0 +1,40 @@
/*
* Copyright (c) 2018-2020 Atmosphère-NX
* Copyright (c) 2020 shchmue
*
* 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/>.
*/
#ifndef _FS_INT64_H_
#define _FS_INT64_H_
#include <utils/types.h>
#include <stdint.h>
/* For 64-bit integers which are 4-byte aligned but not 8-byte aligned. */
typedef struct {
uint32_t low;
uint32_t high;
} fs_int64_t;
static ALWAYS_INLINE void fs_int64_set(fs_int64_t *i, int64_t val) {
i->low = (uint32_t)((val & (uint64_t)(0x00000000FFFFFFFFul)) >> 0);
i->high = (uint32_t)((val & (uint64_t)(0xFFFFFFFF00000000ul)) >> 32);
}
static ALWAYS_INLINE const int64_t fs_int64_get(fs_int64_t *i) {
return ((int64_t)(i->high) << 32) | ((int64_t)i->low);
}
#endif

View File

@ -0,0 +1,237 @@
/*
* Copyright (c) 2019-2020 shchmue
*
* 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/>.
*/
/*
ISC License
hactool Copyright (c) 2018, SciresM
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef _HEADER_H_
#define _HEADER_H_
#include "allocation_table.h"
#include "duplex_storage.h"
#include "fs_int64.h"
#include "hierarchical_integrity_verification_storage.h"
#include "journal_map.h"
#include "journal_storage.h"
#include "remap_storage.h"
#include <assert.h>
#include <stdint.h>
#define MAGIC_DISF 0x46534944
#define MAGIC_DPFS 0x53465044
#define MAGIC_JNGL 0x4C474E4A
#define MAGIC_SAVE 0x45564153
#define MAGIC_RMAP 0x50414D52
#define MAGIC_IVFC 0x43465649
#define VERSION_DISF_LEEGACY 0x40000
#define VERSION_DISF_5 0x50000
#define VERSION_DPFS 0x10000
#define VERSION_JNGL 0x10000
#define VERSION_SAVE 0x60000
#define VERSION_RMAP 0x10000
#define VERSION_IVFC 0x20000
#define SAVE_BLOCK_SIZE_DEFAULT 0x4000
#define SAVE_NUM_HEADERS 2
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;
uint8_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;
static_assert(sizeof(fs_layout_t) == 0x200, "Save filesystem layout header size is wrong!");
typedef struct {
fs_int64_t offset;
fs_int64_t length;
uint32_t block_size_power;
} duplex_info_t;
static_assert(sizeof(duplex_info_t) == 0x14, "Duplex info size is wrong!");
typedef enum {
DUPLEX_LAYER_MASTER = 0,
DUPLEX_LAYER_1 = 1,
DUPLEX_LAYER_2 = 2,
} duplex_layer_t;
typedef struct {
uint32_t magic; /* DPFS */
uint32_t version;
duplex_info_t layers[3];
} duplex_header_t;
typedef struct {
uint64_t level_block_size[2];
} duplex_storage_control_input_param_t;
static_assert(sizeof(duplex_header_t) == 0x44, "Duplex header size is wrong!");
typedef struct {
uint8_t id[0x10];
} account_user_id_t;
typedef enum {
SAVE_TYPE_SYSTEM = 0,
SAVE_TYPE_ACCOUNT = 1,
SAVE_TYPE_BCAT = 2,
SAVE_TYPE_DEVICE = 3,
SAVE_TYPE_TEMP = 4,
SAVE_TYPE_CACHE = 5,
SAVE_TYPE_SYSTEM_BCAT = 6
} save_data_type_t;
typedef enum {
SAVE_RANK_PRIMARY = 0,
SAVE_RANK_SECONDARY = 1,
} save_data_rank_t;
typedef struct {
uint64_t program_id;
account_user_id_t user_id;
uint64_t save_id;
uint8_t save_data_type;
uint8_t save_data_rank;
uint16_t save_data_index;
uint8_t _0x24[0x1C];
} save_data_attribute_t;
static_assert(sizeof(save_data_attribute_t) == 0x40, "Save data attribute size is wrong!");
typedef enum {
SAVE_FLAG_KEEP_AFTER_RESETTING_SYSTEM_SAVE_DATA = 1,
SAVE_FLAG_KEEP_AFTER_REFURBISHMENT = 2,
SAVE_FLAG_KEEP_AFTER_RESETTING_SYSTEM_SAVE_DATA_WITHOUT_USER_SAVE_DATA = 4,
SAVE_FLAG_NEEDS_SECURE_DELETE = 8,
} save_data_flags_t;
typedef struct {
save_data_attribute_t save_data_attribute;
uint64_t save_owner_id;
uint64_t timestamp;
uint32_t flags;
uint8_t _0x54[4];
uint64_t savedata_size;
uint64_t journal_size;
uint64_t commit_id;
uint8_t reserved[0x190];
} extra_data_t;
static_assert(sizeof(extra_data_t) == 0x200, "Extra data size is wrong!");
typedef struct {
uint32_t magic; /* SAVE */
uint32_t version;
uint64_t block_count;
uint64_t block_size;
allocation_table_header_t fat_header;
} save_fs_header_t;
static_assert(sizeof(save_fs_header_t) == 0x48, "Save filesystem header size is wrong!");
typedef struct {
uint8_t cmac[0x10];
uint8_t _0x10[0xF0];
fs_layout_t layout;
duplex_header_t duplex_header;
ivfc_save_hdr_t data_ivfc_header;
uint32_t _0x404;
journal_header_t journal_header;
save_fs_header_t save_header;
remap_header_t main_remap_header, meta_remap_header;
uint64_t _0x6D0;
extra_data_t extra_data_a;
extra_data_t extra_data_b;
union {
struct {
ivfc_save_hdr_t fat_ivfc_header;
uint64_t _0xB98;
uint8_t additional_data[0x3460];
} version_5;
struct {
uint8_t additional_data[0x3528];
} legacy;
};
} save_header_t;
static_assert(sizeof(save_header_t) == 0x4000, "Save header size is wrong!");
#endif

View File

@ -0,0 +1,120 @@
/*
* Copyright (c) 2019-2020 shchmue
*
* 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/>.
*/
/*
ISC License
hactool Copyright (c) 2018, SciresM
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "hierarchical_duplex_storage.h"
#include <gfx_utils.h>
#include <mem/heap.h>
void save_duplex_fs_layer_info_init(duplex_fs_layer_info_t *ctx, uint8_t *data_a, uint8_t *data_b, duplex_info_t *info) {
if (data_a)
ctx->data_a = data_a;
if (data_b)
ctx->data_b = data_b;
ctx->info.offset = info->offset;
ctx->info.length = info->length;
ctx->info.block_size_power = info->block_size_power;
}
bool save_hierarchical_duplex_storage_init(hierarchical_duplex_storage_ctx_t *ctx, remap_storage_ctx_t *storage, save_header_t *header) {
substorage base_storage;
substorage_init(&base_storage, &remap_storage_vt, storage, 0, -1);
fs_layout_t *layout = &header->layout;
duplex_fs_layer_info_t duplex_layers[3];
save_duplex_fs_layer_info_init(&duplex_layers[0], (uint8_t *)header + layout->duplex_master_offset_a, (uint8_t *)header + layout->duplex_master_offset_b, &header->duplex_header.layers[0]);
duplex_layers[1].data_a = malloc(layout->duplex_l1_size);
duplex_layers[1].data_b = malloc(layout->duplex_l1_size);
if (substorage_read(&base_storage, duplex_layers[1].data_a, layout->duplex_l1_offset_a, layout->duplex_l1_size) != layout->duplex_l1_size) {
EPRINTF("Hier dup init: Failed to read L1 bitmap A!");
return false;
}
if (substorage_read(&base_storage, duplex_layers[1].data_b, layout->duplex_l1_offset_b, layout->duplex_l1_size) != layout->duplex_l1_size) {
EPRINTF("Hier dup init: Failed to read L1 bitmap B!");
return false;
}
save_duplex_fs_layer_info_init(&duplex_layers[1], NULL, NULL, &header->duplex_header.layers[1]);
duplex_layers[2].data_a = malloc(layout->duplex_data_size);
duplex_layers[2].data_b = malloc(layout->duplex_data_size);
if (substorage_read(&base_storage, duplex_layers[2].data_a, layout->duplex_data_offset_a, layout->duplex_data_size) != layout->duplex_data_size) {
EPRINTF("Hier dup init: Failed to read duplex data A!");
return false;
}
if (substorage_read(&base_storage, duplex_layers[2].data_b, layout->duplex_data_offset_b, layout->duplex_data_size) != layout->duplex_data_size) {
EPRINTF("Hier dup init: Failed to read duplex data B!");
return false;
}
save_duplex_fs_layer_info_init(&duplex_layers[2], NULL, NULL, &header->duplex_header.layers[2]);
uint8_t *bitmap = layout->duplex_index == 1 ? duplex_layers[0].data_b : duplex_layers[0].data_a;
ctx->layers[0]._length = layout->duplex_l1_size;
save_duplex_storage_init(&ctx->layers[0], duplex_layers[1].data_a, duplex_layers[1].data_b, duplex_layers[1].info.block_size_power, bitmap, layout->duplex_master_size);
bitmap = malloc(ctx->layers[0]._length);
if (save_duplex_storage_read(&ctx->layers[0], bitmap, 0, ctx->layers[0]._length) != ctx->layers[0]._length) {
EPRINTF("Hier dup init: Failed to read bitmap!");
return false;
}
ctx->layers[1]._length = layout->duplex_data_size;
save_duplex_storage_init(&ctx->layers[1], duplex_layers[2].data_a, duplex_layers[2].data_b, duplex_layers[2].info.block_size_power, bitmap, ctx->layers[0]._length);
ctx->data_layer = &ctx->layers[1];
ctx->_length = ctx->data_layer->_length;
return true;
}
bool save_hierarchical_duplex_storage_flush(hierarchical_duplex_storage_ctx_t *ctx, remap_storage_ctx_t *storage, save_header_t *header) {
substorage base_storage;
substorage_init(&base_storage, &remap_storage_vt, storage, 0, -1);
fs_layout_t *layout = &header->layout;
if (save_duplex_storage_write(&ctx->layers[0], &ctx->layers[1].bitmap.data, 0, ctx->layers[0]._length) != ctx->layers[0]._length) {
EPRINTF("Hier dup flush: Failed to write bitmap!");
return false;
}
if (substorage_write(&base_storage, ctx->layers[1].data_a.base_storage.ctx, layout->duplex_data_offset_a, layout->duplex_data_size) != layout->duplex_data_size) {
EPRINTF("Hier dup flush: Failed to write data A!");
return false;
}
if (substorage_write(&base_storage, ctx->layers[1].data_b.base_storage.ctx, layout->duplex_data_offset_b, layout->duplex_data_size) != layout->duplex_data_size) {
EPRINTF("Hier dup flush: Failed to write data B!");
return false;
}
return true;
}

View File

@ -0,0 +1,61 @@
/*
* Copyright (c) 2019-2020 shchmue
*
* 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/>.
*/
/*
ISC License
hactool Copyright (c) 2018, SciresM
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef _HIER_DUPLEX_STORAGE_H_
#define _HIER_DUPLEX_STORAGE_H_
#include "duplex_storage.h"
#include "header.h"
#include "remap_storage.h"
#include "storage.h"
#include <stdint.h>
typedef struct {
duplex_storage_ctx_t layers[2];
duplex_storage_ctx_t *data_layer;
uint64_t _length;
substorage storage;
} hierarchical_duplex_storage_ctx_t;
typedef struct {
uint8_t *data_a;
uint8_t *data_b;
duplex_info_t info;
} duplex_fs_layer_info_t;
bool save_hierarchical_duplex_storage_init(hierarchical_duplex_storage_ctx_t *ctx, remap_storage_ctx_t *storage, save_header_t *header);
bool save_hierarchical_duplex_storage_flush(hierarchical_duplex_storage_ctx_t *ctx, remap_storage_ctx_t *storage, save_header_t *header);
#endif

View File

@ -0,0 +1,174 @@
/*
* Copyright (c) 2019-2020 shchmue
*
* 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/>.
*/
/*
ISC License
hactool Copyright (c) 2018, SciresM
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "hierarchical_integrity_verification_storage.h"
#include "header.h"
#include <mem/heap.h>
#include <sec/se.h>
#include <string.h>
void save_hierarchical_integrity_verification_storage_control_area_query_size(ivfc_size_set_t *out, const ivfc_storage_control_input_param_t *input_param, int32_t layer_count, uint64_t data_size) {
int64_t level_size[IVFC_MAX_LEVEL + 1];
int32_t level = layer_count - 1;
out->control_size = sizeof(ivfc_save_hdr_t);
level_size[level] = ALIGN(data_size, input_param->level_block_size[level - 1]);
level--;
for ( ; level > 0; --level) {
level_size[level] = ALIGN(level_size[level + 1] / input_param->level_block_size[level] * 0x20, input_param->level_block_size[level - 1]);
}
level_size[0] = 0x20 * level_size[1] / input_param->level_block_size[0];
out->master_hash_size = level_size[0];
for (level = 1; level < layer_count - 1; ++level) {
out->layered_hash_sizes[level - 1] = level_size[level];
}
}
bool save_hierarchical_integrity_verification_storage_control_area_expand(substorage *storage, const ivfc_save_hdr_t *header) {
return substorage_write(storage, header, 0, sizeof(ivfc_save_hdr_t)) == sizeof(ivfc_save_hdr_t);
}
void save_hierarchical_integrity_verification_storage_init(hierarchical_integrity_verification_storage_ctx_t *ctx, integrity_verification_info_ctx_t *level_info, uint64_t num_levels, int integrity_check_level) {
ctx->integrity_check_level = integrity_check_level;
ctx->level_validities = malloc(sizeof(validity_t *) * (num_levels - 1));
memcpy(&ctx->levels[0].base_storage, &level_info[0].data, sizeof(substorage));
for (unsigned int i = 1; i < num_levels; i++) {
integrity_verification_storage_ctx_t *level_data = &ctx->integrity_storages[i - 1];
save_ivfc_storage_init(level_data, &level_info[i], &ctx->levels[i - 1].base_storage, integrity_check_level);
uint64_t level_size = level_data->base_storage.length;
uint32_t cache_count = MIN((uint32_t)(DIV_ROUND_UP(level_size, level_info[i].block_size)), 4);
save_cached_storage_init_from_sector_storage(&ctx->levels[i], &level_data->base_storage, cache_count);
substorage_init(&ctx->levels[i].base_storage, &ivfc_storage_vt, level_data, 0, level_info[i].data.length);
ctx->level_validities[i - 1] = level_data->block_validities;
}
ctx->data_level = &ctx->levels[num_levels - 1];
ctx->length = ctx->data_level->length;
substorage_init(&ctx->base_storage, &hierarchical_integrity_verification_storage_vt, ctx, 0, ctx->length);
}
void save_hierarchical_integrity_verification_storage_get_ivfc_info(integrity_verification_info_ctx_t *init_info, ivfc_save_hdr_t *header, uint64_t num_levels, substorage *levels) {
struct salt_source_t {
char string[64];
uint32_t length;
};
static const struct salt_source_t salt_sources[IVFC_MAX_LEVEL] = {
{"HierarchicalIntegrityVerificationStorage::Master", 48},
{"HierarchicalIntegrityVerificationStorage::L1", 44},
{"HierarchicalIntegrityVerificationStorage::L2", 44},
{"HierarchicalIntegrityVerificationStorage::L3", 44},
{"HierarchicalIntegrityVerificationStorage::L4", 44},
{"HierarchicalIntegrityVerificationStorage::L5", 44}
};
memcpy(&init_info[0].data, &levels[0], sizeof(substorage));
init_info[0].block_size = 0;
for (unsigned int i = 1; i < num_levels; i++) {
memcpy(&init_info[i].data, &levels[i], sizeof(substorage));
init_info[i].block_size = 1 << header->level_hash_info.level_headers[i - 1].block_size;
se_calc_hmac_sha256(init_info[i].salt, &header->level_hash_info.seed, sizeof(hash_salt_t), salt_sources[i - 1].string, salt_sources[i - 1].length);
}
}
static void save_hierarchical_integrity_verification_storage_to_storage_list(substorage *levels, ivfc_save_hdr_t *header, substorage *master_hash, substorage *data) {
memcpy(&levels[0], master_hash, sizeof(substorage));
for (unsigned int i = 0; i < 3; i++) {
ivfc_level_hdr_t *level = &header->level_hash_info.level_headers[i];
substorage_init(&levels[i + 1], &remap_storage_vt, data, fs_int64_get(&level->logical_offset), fs_int64_get(&level->hash_data_size));
}
}
void save_hierarchical_integrity_verification_storage_init_with_levels(hierarchical_integrity_verification_storage_ctx_t *ctx, ivfc_save_hdr_t *header, uint64_t num_levels, substorage *levels, int integrity_check_level) {
integrity_verification_info_ctx_t init_info[IVFC_MAX_LEVEL];
save_hierarchical_integrity_verification_storage_get_ivfc_info(init_info, header, num_levels, levels);
save_hierarchical_integrity_verification_storage_init(ctx, init_info, num_levels, integrity_check_level);
}
void save_hierarchical_integrity_verification_storage_init_for_fat(hierarchical_integrity_verification_storage_ctx_t *ctx, ivfc_save_hdr_t *header, substorage *master_hash, substorage *data, int integrity_check_level) {
const uint32_t ivfc_levels = IVFC_MAX_LEVEL - 2;
substorage levels[ivfc_levels + 1];
save_hierarchical_integrity_verification_storage_to_storage_list(levels, header, master_hash, data);
save_hierarchical_integrity_verification_storage_init_with_levels(ctx, header, ivfc_levels, levels, integrity_check_level);
}
validity_t save_hierarchical_integrity_verification_storage_validate(hierarchical_integrity_verification_storage_ctx_t *ctx) {
validity_t result = VALIDITY_VALID;
integrity_verification_storage_ctx_t *storage = &ctx->integrity_storages[3];
uint64_t block_size = storage->base_storage.sector_size;
uint32_t block_count = (uint32_t)(DIV_ROUND_UP(ctx->length, block_size));
uint8_t *buffer = malloc(block_size);
for (unsigned int i = 0; i < block_count; i++) {
if (ctx->level_validities[3][i] == VALIDITY_UNCHECKED) {
uint64_t storage_size = storage->base_storage.length;
uint32_t to_read = MIN((uint32_t)(storage_size - block_size * i), (uint32_t)block_size);
substorage_read(&ctx->data_level->base_storage, buffer, block_size * i, to_read);
}
if (ctx->level_validities[3][i] == VALIDITY_INVALID) {
result = VALIDITY_INVALID;
break;
}
}
free(buffer);
return result;
}
void save_hierarchical_integrity_verification_storage_set_level_validities(hierarchical_integrity_verification_storage_ctx_t *ctx) {
for (unsigned int i = 0; i < IVFC_MAX_LEVEL - 2; i++) {
validity_t level_validity = VALIDITY_VALID;
for (unsigned int j = 0; j < ctx->integrity_storages[i].base_storage.sector_count; j++) {
if (ctx->level_validities[i][j] == VALIDITY_INVALID) {
level_validity = VALIDITY_INVALID;
break;
}
if (ctx->level_validities[i][j] == VALIDITY_UNCHECKED && level_validity != VALIDITY_INVALID) {
level_validity = VALIDITY_UNCHECKED;
}
}
ctx->hash_validity[i] = level_validity;
}
}

View File

@ -0,0 +1,110 @@
/*
* Copyright (c) 2019-2020 shchmue
*
* 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/>.
*/
/*
ISC License
hactool Copyright (c) 2018, SciresM
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef _HIVFC_H_
#define _HIVFC_H_
#include "cached_storage.h"
#include "fs_int64.h"
#include "integrity_verification_storage.h"
#include "storage.h"
#include <utils/types.h>
#include <assert.h>
#include <stdint.h>
#define IVFC_MAX_LEVEL 6
typedef struct {
fs_int64_t logical_offset;
fs_int64_t hash_data_size;
uint32_t block_size;
uint8_t reserved[4];
} ivfc_level_hdr_t;
typedef struct {
ivfc_level_hdr_t *hdr;
validity_t hash_validity;
} ivfc_level_save_ctx_t;
typedef struct {
uint8_t val[0x20];
} hash_salt_t;
typedef struct {
uint32_t num_levels;
ivfc_level_hdr_t level_headers[IVFC_MAX_LEVEL];
hash_salt_t seed;
} ivfc_level_hash_info_t;
typedef struct {
uint32_t magic;
uint32_t version;
uint32_t master_hash_size;
ivfc_level_hash_info_t level_hash_info;
} ivfc_save_hdr_t;
static_assert(sizeof(ivfc_save_hdr_t) == 0xC0, "Ivfc header size invalid!");
typedef struct {
int64_t control_size;
int64_t master_hash_size;
int64_t layered_hash_sizes[IVFC_MAX_LEVEL];
} ivfc_size_set_t;
typedef struct {
uint64_t level_block_size[IVFC_MAX_LEVEL];
} ivfc_storage_control_input_param_t;
typedef struct {
cached_storage_ctx_t levels[IVFC_MAX_LEVEL - 1];
cached_storage_ctx_t *data_level;
int integrity_check_level;
validity_t **level_validities;
uint64_t length;
integrity_verification_storage_ctx_t integrity_storages[IVFC_MAX_LEVEL - 2];
validity_t hash_validity[IVFC_MAX_LEVEL - 2];
substorage base_storage;
} hierarchical_integrity_verification_storage_ctx_t;
void save_hierarchical_integrity_verification_storage_control_area_query_size(ivfc_size_set_t *out, const ivfc_storage_control_input_param_t *input_param, int32_t layer_count, uint64_t data_size);
bool save_hierarchical_integrity_verification_storage_control_area_expand(substorage *header_storage, const ivfc_save_hdr_t *header);
void save_hierarchical_integrity_verification_storage_init(hierarchical_integrity_verification_storage_ctx_t *ctx, integrity_verification_info_ctx_t *level_info, uint64_t num_levels, int integrity_check_level);
void save_hierarchical_integrity_verification_storage_init_with_levels(hierarchical_integrity_verification_storage_ctx_t *ctx, ivfc_save_hdr_t *header, uint64_t num_levels, substorage *levels, int integrity_check_level);
void save_hierarchical_integrity_verification_storage_init_for_fat(hierarchical_integrity_verification_storage_ctx_t *ctx, ivfc_save_hdr_t *header, substorage *master_hash, substorage *data, int integrity_check_level);
validity_t save_hierarchical_integrity_verification_storage_validate(hierarchical_integrity_verification_storage_ctx_t *ctx);
void save_hierarchical_integrity_verification_storage_set_level_validities(hierarchical_integrity_verification_storage_ctx_t *ctx);
#endif

View File

@ -0,0 +1,483 @@
/*
* Copyright (c) 2019-2020 shchmue
*
* 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/>.
*/
/*
ISC License
hactool Copyright (c) 2018, SciresM
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "hierarchical_save_file_table.h"
#include "path_parser.h"
#include <gfx_utils.h>
#include <string.h>
bool save_hierarchical_file_table_find_path_recursive(hierarchical_save_file_table_ctx_t *ctx, const char *path, save_entry_key_t *key) {
path_parser_ctx_t parser;
if (!save_path_parser_init(&parser, path)) {
EPRINTF("Failed to init path parser!");
return false;
}
uint32_t current_len = 0;
const char *current = save_path_parser_get_current(&parser, &current_len);
memset(key, 0, sizeof(save_entry_key_t));
memcpy(key->name, current, current_len);
while (!save_path_parser_is_finished(&parser)) {
key->parent = save_fs_list_get_index_from_key(&ctx->directory_table, key, NULL);
if (key->parent & 0x80000000)
return false;
save_path_parser_try_get_next(&parser, key->name);
}
return true;
}
bool save_hierarchical_file_table_try_open_file(hierarchical_save_file_table_ctx_t *ctx, const char *path, save_file_info_t *file_info) {
save_entry_key_t key;
if (!save_hierarchical_file_table_find_path_recursive(ctx, path, &key)) {
memset(file_info, 0, sizeof(save_file_info_t));
return false;
}
save_table_entry_t value;
if (save_fs_list_try_get_value_by_key(&ctx->file_table, &key, &value)) {
memcpy(file_info, &value.save_file_info, sizeof(save_file_info_t));
return true;
}
memset(file_info, 0, sizeof(save_file_info_t));
return false;
}
bool save_hierarchical_file_table_try_open_directory(hierarchical_save_file_table_ctx_t *ctx, const char *path, save_find_position_t *position) {
save_entry_key_t key;
if (!save_hierarchical_file_table_find_path_recursive(ctx, path, &key)) {
memset(position, 0, sizeof(save_find_position_t));
return false;
}
save_table_entry_t entry;
if (save_fs_list_try_get_value_by_key(&ctx->file_table, &key, &entry)) {
memcpy(position, &entry.save_find_position, sizeof(save_find_position_t));
return true;
}
memset(position, 0, sizeof(save_find_position_t));
return false;
}
bool save_hierarchical_file_table_find_next_file(hierarchical_save_file_table_ctx_t *ctx, save_find_position_t *position, save_file_info_t *info, char *name) {
if (position->next_file == 0) {
memset(info, 0, sizeof(save_file_info_t));
memset(name, 0, SAVE_FS_LIST_MAX_NAME_LENGTH);
return false;
}
save_table_entry_t entry;
if (!save_fs_list_try_get_value_and_name(&ctx->file_table, position->next_file, &entry, name)) {
memset(info, 0, sizeof(save_file_info_t));
memset(name, 0, SAVE_FS_LIST_MAX_NAME_LENGTH);
return false;
}
position->next_file = entry.next_sibling;
memcpy(info, &entry.save_file_info, sizeof(save_file_info_t));
return true;
}
bool save_hierarchical_file_table_find_next_directory(hierarchical_save_file_table_ctx_t *ctx, save_find_position_t *position, char *name) {
if (position->next_directory == 0) {
return false;
}
save_table_entry_t entry;
if(!save_fs_list_try_get_value_and_name(&ctx->directory_table, position->next_directory, &entry, name)) {
memset(name, 0, SAVE_FS_LIST_MAX_NAME_LENGTH);
return false;
}
position->next_directory = entry.next_sibling;
return true;
}
static bool save_hierarchical_file_table_link_file_to_parent(hierarchical_save_file_table_ctx_t *ctx, uint32_t parent_index, uint32_t file_index) {
save_table_entry_t parent_entry, file_entry;
if (!save_fs_list_get_value_by_index(&ctx->directory_table, parent_index, &parent_entry)) {
EPRINTF("Failed to get directory table value!");
return false;
}
if (!save_fs_list_get_value_by_index(&ctx->file_table, file_index, &file_entry)) {
EPRINTF("Failed to get file table value!");
return false;
}
file_entry.next_sibling = parent_entry.save_find_position.next_file;
parent_entry.save_find_position.next_file = file_index;
if (!save_fs_list_set_value(&ctx->directory_table, parent_index, &parent_entry)) {
EPRINTF("Failed to set directory table value!");
return false;
}
if (!save_fs_list_set_value(&ctx->file_table, file_index, &file_entry)) {
EPRINTF("Failed to set file table value!");
return false;
}
return true;
}
static bool save_hierarchical_file_table_link_directory_to_parent(hierarchical_save_file_table_ctx_t *ctx, uint32_t parent_index, uint32_t dir_index) {
save_table_entry_t parent_entry, dir_entry;
if (!save_fs_list_get_value_by_index(&ctx->directory_table, parent_index, &parent_entry)) {
EPRINTF("Failed to get parent directory table value!");
return false;
}
if (!save_fs_list_get_value_by_index(&ctx->directory_table, dir_index, &dir_entry)) {
EPRINTF("Failed to get directory table value!");
return false;
}
dir_entry.next_sibling = parent_entry.save_find_position.next_directory;
parent_entry.save_find_position.next_directory = dir_index;
if (!save_fs_list_set_value(&ctx->directory_table, parent_index, &parent_entry)) {
EPRINTF("Failed to set parent directory table value!");
return false;
}
if (!save_fs_list_set_value(&ctx->directory_table, dir_index, &dir_entry)) {
EPRINTF("Failed to set directory table value!");
return false;
}
return true;
}
void save_hierarchical_file_table_unlink_file_from_parent(hierarchical_save_file_table_ctx_t *ctx, uint32_t parent_index, uint32_t file_index) {
save_table_entry_t parent_entry, file_entry;
save_fs_list_get_value_by_index(&ctx->directory_table, parent_index, &parent_entry);
save_fs_list_get_value_by_index(&ctx->file_table, file_index, &file_entry);
if (parent_entry.save_find_position.next_file == file_index) {
parent_entry.save_find_position.next_file = file_entry.next_sibling;
save_fs_list_set_value(&ctx->directory_table, parent_index, &parent_entry);
return;
}
uint32_t prev_index = parent_entry.save_find_position.next_file;
save_table_entry_t prev_entry, cur_entry;
save_fs_list_get_value_by_index(&ctx->file_table, prev_index, &prev_entry);
uint32_t cur_index = prev_entry.next_sibling;
while (cur_index != 0) {
save_fs_list_get_value_by_index(&ctx->file_table, cur_index, &cur_entry);
if (cur_index == file_index) {
prev_entry.next_sibling = cur_entry.next_sibling;
save_fs_list_set_value(&ctx->file_table, prev_index, &prev_entry);
return;
}
prev_index = cur_index;
memcpy(&prev_entry, &cur_entry, sizeof(prev_entry));
cur_index = prev_entry.next_sibling;
}
}
void save_hierarchical_file_table_unlink_directory_from_parent(hierarchical_save_file_table_ctx_t *ctx, uint32_t parent_index, uint32_t dir_index) {
save_table_entry_t parent_entry, dir_entry;
save_fs_list_get_value_by_index(&ctx->directory_table, parent_index, &parent_entry);
save_fs_list_get_value_by_index(&ctx->directory_table, dir_index, &dir_entry);
if (parent_entry.save_find_position.next_directory == dir_index) {
parent_entry.save_find_position.next_directory = dir_entry.next_sibling;
save_fs_list_set_value(&ctx->directory_table, parent_index, &parent_entry);
return;
}
uint32_t prev_index = parent_entry.save_find_position.next_directory;
save_table_entry_t prev_entry, cur_entry;
save_fs_list_get_value_by_index(&ctx->directory_table, prev_index, &prev_entry);
uint32_t cur_index = prev_entry.next_sibling;
while (cur_index != 0) {
save_fs_list_get_value_by_index(&ctx->directory_table, cur_index, &cur_entry);
if (cur_index == dir_index) {
prev_entry.next_sibling = cur_entry.next_sibling;
save_fs_list_set_value(&ctx->directory_table, prev_index, &prev_entry);
return;
}
prev_index = cur_index;
memcpy(&prev_entry, &cur_entry, sizeof(prev_entry));
cur_index = prev_entry.next_sibling;
}
}
bool save_hierarchical_file_table_delete_file(hierarchical_save_file_table_ctx_t *ctx, const char *path) {
save_entry_key_t key;
save_hierarchical_file_table_find_path_recursive(ctx, path, &key);
uint32_t parent_index = key.parent;
uint32_t to_delete_index = save_fs_list_get_index_from_key(&ctx->file_table, &key, NULL);
if (to_delete_index == 0xFFFFFFFF) {
EPRINTF("File not found!");
return false;
}
save_hierarchical_file_table_unlink_file_from_parent(ctx, parent_index, to_delete_index);
return save_fs_list_remove(&ctx->file_table, &key);
}
bool save_hierarchical_file_table_delete_directory(hierarchical_save_file_table_ctx_t *ctx, const char *path) {
save_entry_key_t key;
save_hierarchical_file_table_find_path_recursive(ctx, path, &key);
uint32_t parent_index = key.parent;
uint32_t to_delete_index = save_fs_list_get_index_from_key(&ctx->directory_table, &key, NULL);
if (to_delete_index == 0xFFFFFFFF) {
EPRINTF("Directory not found!");
return false;
}
save_table_entry_t to_delete_entry;
save_fs_list_get_value_by_index(&ctx->directory_table, to_delete_index, &to_delete_entry);
if (to_delete_entry.save_find_position.next_directory != 0 || to_delete_entry.save_find_position.next_file != 0) {
EPRINTF("Directory is not empty!");
return false;
}
save_hierarchical_file_table_unlink_directory_from_parent(ctx, parent_index, to_delete_index);
return save_fs_list_remove(&ctx->directory_table, &key);
}
bool save_hierarchical_file_table_rename_file(hierarchical_save_file_table_ctx_t *ctx, const char *src_path, const char *dst_path) {
save_file_info_t file_info;
save_find_position_t position;
save_entry_key_t old_key, new_key;
if (strcmp(src_path, dst_path) == 0 || save_hierarchical_file_table_try_open_file(ctx, dst_path, &file_info) || save_hierarchical_file_table_try_open_directory(ctx, dst_path, &position)) {
EPRINTF("Destination path already exists!");
return false;
}
if (!save_hierarchical_file_table_find_path_recursive(ctx, src_path, &old_key)) {
EPRINTF("File not found!");
return false;
}
uint32_t file_index = save_fs_list_get_index_from_key(&ctx->file_table, &old_key, NULL);
if (!save_hierarchical_file_table_find_path_recursive(ctx, dst_path, &new_key)) {
EPRINTF("File not found!");
return false;
}
if (old_key.parent != new_key.parent) {
save_hierarchical_file_table_unlink_file_from_parent(ctx, old_key.parent, file_index);
save_hierarchical_file_table_link_file_to_parent(ctx, new_key.parent, file_index);
}
return save_fs_list_change_key(&ctx->file_table, &old_key, &new_key);
}
static ALWAYS_INLINE bool save_is_sub_path(const char *path1, const char *path2) {
/* Check if either path is subpath of the other. */
uint64_t path1_len = strlen(path1), path2_len = strlen(path2);
if (path1_len == 0 || path2_len == 0)
return true;
if (path1[path1_len - 1] == '/')
path1_len--;
if (path2[path2_len - 1] == '/')
path2_len--;
const char *short_path, *long_path;
uint64_t short_path_len, long_path_len;
if (path1_len < path2_len) {
short_path = path1;
short_path_len = path1_len;
long_path = path2;
long_path_len = path2_len;
} else {
short_path = path2;
short_path_len = path2_len;
long_path = path1;
long_path_len = path1_len;
}
if (strncmp(short_path, long_path, short_path_len) != 0)
return false;
return long_path_len > short_path_len + 1 && long_path[short_path_len] == '/';
}
bool save_hierarchical_file_table_rename_directory(hierarchical_save_file_table_ctx_t *ctx, const char *src_path, const char *dst_path) {
save_file_info_t file_info;
save_find_position_t position;
save_entry_key_t old_key, new_key;
if (strcmp(src_path, dst_path) == 0 || save_hierarchical_file_table_try_open_file(ctx, dst_path, &file_info) || save_hierarchical_file_table_try_open_directory(ctx, dst_path, &position)) {
EPRINTF("Destination path already exists!");
return false;
}
if (!save_hierarchical_file_table_find_path_recursive(ctx, src_path, &old_key)) {
EPRINTF("File not found!");
return false;
}
uint32_t dir_index = save_fs_list_get_index_from_key(&ctx->file_table, &old_key, NULL);
if (!save_hierarchical_file_table_find_path_recursive(ctx, dst_path, &new_key)) {
EPRINTF("File not found!");
return false;
}
if (save_is_sub_path(src_path, dst_path)) {
EPRINTF("Destination is subpath of source!");
return false;
}
if (old_key.parent != new_key.parent) {
save_hierarchical_file_table_unlink_directory_from_parent(ctx, old_key.parent, dir_index);
save_hierarchical_file_table_link_directory_to_parent(ctx, new_key.parent, dir_index);
}
return save_fs_list_change_key(&ctx->directory_table, &old_key, &new_key);
}
uint32_t save_hierarchical_file_table_create_parent_directory_recursive(hierarchical_save_file_table_ctx_t *ctx, path_parser_ctx_t *parser, save_entry_key_t *key) {
uint32_t prev_index = 0;
while (!save_path_parser_is_finished(parser)) {
uint32_t index = save_fs_list_get_index_from_key(&ctx->directory_table, key, NULL);
if (index == 0xFFFFFFFF) {
save_table_entry_t new_entry;
memset(&new_entry, 0, sizeof(new_entry));
index = save_fs_list_add(&ctx->directory_table, key, &new_entry);
if ((prev_index & 0x80000000) == 0)
save_hierarchical_file_table_link_directory_to_parent(ctx, prev_index, index);
}
prev_index = index;
key->parent = index;
save_path_parser_try_get_next(parser, key->name);
}
return prev_index;
}
static bool save_hierarchical_file_table_create_file_recursive(hierarchical_save_file_table_ctx_t *ctx, const char *path, save_file_info_t *file_info) {
path_parser_ctx_t parser;
if (!save_path_parser_init(&parser, path)) {
EPRINTF("Failed to init path parser!");
return false;
}
save_entry_key_t key = {"", 0};
uint32_t current_len = 0;
const char *current = save_path_parser_get_current(&parser, &current_len);
memcpy(key.name, current, current_len);
uint32_t parent_index = save_hierarchical_file_table_create_parent_directory_recursive(ctx, &parser, &key);
uint32_t index = save_fs_list_get_index_from_key(&ctx->file_table, &key, NULL);
save_table_entry_t file_entry;
memset(&file_entry, 0, sizeof(file_entry));
if ((index & 0x80000000) == 0) {
save_fs_list_get_value_by_index(&ctx->file_table, index, &file_entry);
memcpy(&file_entry.save_file_info, file_info, sizeof(save_file_info_t));
save_fs_list_set_value(&ctx->file_table, index, &file_entry);
return true;
}
memcpy(&file_entry.save_file_info, file_info, sizeof(save_file_info_t));
index = save_fs_list_add(&ctx->file_table, &key, &file_entry);
if (index == 0) {
EPRINTF("Failed to add file to FS list!");
return false;
}
return save_hierarchical_file_table_link_file_to_parent(ctx, parent_index, index);
}
static bool save_hierarchical_file_table_create_directory_recursive(hierarchical_save_file_table_ctx_t *ctx, const char *path) {
path_parser_ctx_t parser;
if (!save_path_parser_init(&parser, path)) {
EPRINTF("Failed to init path parser!");
return false;
}
save_entry_key_t key = {"", 0};
uint32_t current_len = 0;
const char *current = save_path_parser_get_current(&parser, &current_len);
memcpy(key.name, current, current_len);
uint32_t parent_index = save_hierarchical_file_table_create_parent_directory_recursive(ctx, &parser, &key);
uint32_t index = save_fs_list_get_index_from_key(&ctx->directory_table, &key, NULL);
if (index != 0xFFFFFFFF)
return true;
save_table_entry_t dir_entry;
memset(&dir_entry, 0, sizeof(dir_entry));
save_fs_list_add(&ctx->directory_table, &key, &dir_entry);
return save_hierarchical_file_table_link_directory_to_parent(ctx, parent_index, index);
}
bool save_hierarchical_file_table_add_file(hierarchical_save_file_table_ctx_t *ctx, const char *path, save_file_info_t *file_info) {
if (strlen(path) == 1 && path[0] == '/') {
EPRINTF("Path cannot be empty!");
return false;
}
return save_hierarchical_file_table_create_file_recursive(ctx, path, file_info);
}
bool save_hierarchical_file_table_add_directory(hierarchical_save_file_table_ctx_t *ctx, const char *path) {
if (strlen(path) == 1 && path[0] == '/') {
EPRINTF("Directory path cannot be empty!");
return false;
}
save_hierarchical_file_table_create_directory_recursive(ctx, path);
return true;
}

View File

@ -0,0 +1,59 @@
/*
* Copyright (c) 2019-2020 shchmue
*
* 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/>.
*/
/*
ISC License
hactool Copyright (c) 2018, SciresM
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef _HIERARCHICAL_SAVE_FILE_TABLE_H_
#define _HIERARCHICAL_SAVE_FILE_TABLE_H_
#include "save_fs_entry.h"
#include "save_fs_list.h"
#include <stdint.h>
typedef struct {
save_filesystem_list_ctx_t file_table;
save_filesystem_list_ctx_t directory_table;
} hierarchical_save_file_table_ctx_t;
bool save_hierarchical_file_table_try_open_file(hierarchical_save_file_table_ctx_t *ctx, const char *path, save_file_info_t *file_info);
bool save_hierarchical_file_table_try_open_directory(hierarchical_save_file_table_ctx_t *ctx, const char *path, save_find_position_t *position);
bool save_hierarchical_file_table_find_next_file(hierarchical_save_file_table_ctx_t *ctx, save_find_position_t *position, save_file_info_t *info, char *name);
bool save_hierarchical_file_table_find_next_directory(hierarchical_save_file_table_ctx_t *ctx, save_find_position_t *position, char *name);
bool save_hierarchical_file_table_delete_file(hierarchical_save_file_table_ctx_t *ctx, const char *path);
bool save_hierarchical_file_table_delete_directory(hierarchical_save_file_table_ctx_t *ctx, const char *path);
bool save_hierarchical_file_table_rename_file(hierarchical_save_file_table_ctx_t *ctx, const char *src_path, const char *dst_path);
bool save_hierarchical_file_table_rename_directory(hierarchical_save_file_table_ctx_t *ctx, const char *src_path, const char *dst_path);
bool save_hierarchical_file_table_add_file(hierarchical_save_file_table_ctx_t *ctx, const char *path, save_file_info_t *file_info);
bool save_hierarchical_file_table_add_directory(hierarchical_save_file_table_ctx_t *ctx, const char *path);
#endif

View File

@ -0,0 +1,155 @@
/*
* Copyright (c) 2019-2020 shchmue
*
* 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/>.
*/
/*
ISC License
hactool Copyright (c) 2018, SciresM
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "integrity_verification_storage.h"
#include <gfx_utils.h>
#include <mem/heap.h>
#include <sec/se.h>
#include <utils/types.h>
#include <string.h>
void save_ivfc_storage_init(integrity_verification_storage_ctx_t *ctx, integrity_verification_info_ctx_t *info, substorage *hash_storage, int integrity_check_level) {
sector_storage_init(&ctx->base_storage, &info->data, info->block_size);
memcpy(&ctx->hash_storage, hash_storage, sizeof(substorage));
ctx->integrity_check_level = integrity_check_level;
memcpy(ctx->salt, info->salt, sizeof(ctx->salt));
ctx->block_validities = calloc(1, sizeof(validity_t) * ctx->base_storage.sector_count);
}
/* buffer must have size count + 0x20 for salt to by copied in at offset 0. */
static ALWAYS_INLINE void save_ivfc_storage_do_hash(integrity_verification_storage_ctx_t *ctx, uint8_t *out_hash, void *buffer, uint64_t count) {
memcpy(buffer, ctx->salt, sizeof(ctx->salt));
se_calc_sha256(out_hash, buffer, count + sizeof(ctx->salt));
out_hash[0x1F] |= 0x80;
}
static ALWAYS_INLINE bool is_empty(const void *buffer, uint64_t count) {
bool empty = true;
const uint8_t *buf = (const uint8_t *)buffer;
for (uint64_t i = 0; i < count; i++) {
if (buf[i] != 0) {
empty = false;
break;
}
}
return empty;
}
bool save_ivfc_storage_read(integrity_verification_storage_ctx_t *ctx, void *buffer, uint64_t offset, uint64_t count) {
if (count > ctx->base_storage.sector_size) {
EPRINTF("IVFC read exceeds sector size!");
return false;
}
uint64_t block_index = offset / ctx->base_storage.sector_size;
if (ctx->block_validities[block_index] == VALIDITY_INVALID && ctx->integrity_check_level) {
EPRINTF("IVFC hash error!");
return false;
}
uint8_t hash_buffer[0x20] = {0};
uint64_t hash_pos = block_index * sizeof(hash_buffer);
if (substorage_read(&ctx->hash_storage, hash_buffer, hash_pos, sizeof(hash_buffer)) != sizeof(hash_buffer))
return false;
if (is_empty(hash_buffer, sizeof(hash_buffer))) {
memset(buffer, 0, count);
ctx->block_validities[block_index] = VALIDITY_VALID;
return true;
}
uint8_t *data_buffer = calloc(1, ctx->base_storage.sector_size + 0x20);
if (substorage_read(&ctx->base_storage.base_storage, data_buffer + 0x20, offset - (offset % ctx->base_storage.sector_size), ctx->base_storage.sector_size) != ctx->base_storage.sector_size) {
free(data_buffer);
return false;
}
if (ctx->integrity_check_level && ctx->block_validities[block_index] != VALIDITY_UNCHECKED) {
memcpy(buffer, data_buffer + 0x20 + (offset % ctx->base_storage.sector_size), count);
free(data_buffer);
return true;
}
uint8_t hash[0x20] = {0};
save_ivfc_storage_do_hash(ctx, hash, data_buffer, ctx->base_storage.sector_size);
memcpy(buffer, data_buffer + 0x20 + (offset % ctx->base_storage.sector_size), count);
free(data_buffer);
if (memcmp(hash_buffer, hash, sizeof(hash_buffer)) == 0) {
ctx->block_validities[block_index] = VALIDITY_VALID;
} else {
ctx->block_validities[block_index] = VALIDITY_INVALID;
if (ctx->integrity_check_level) {
EPRINTF("IVFC hash error!");
return false;
}
}
return true;
}
bool save_ivfc_storage_write(integrity_verification_storage_ctx_t *ctx, const void *buffer, uint64_t offset, uint64_t count) {
uint64_t block_index = offset / ctx->base_storage.sector_size;
uint64_t hash_pos = block_index * 0x20;
uint8_t hash[0x20] = {0};
uint8_t *data_buffer = calloc(1, ctx->base_storage.sector_size + 0x20);
if (count < ctx->base_storage.sector_size) {
if (substorage_read(&ctx->base_storage.base_storage, data_buffer + 0x20, offset - (offset % ctx->base_storage.sector_size), ctx->base_storage.sector_size) != ctx->base_storage.sector_size) {
free(data_buffer);
return false;
}
}
memcpy(data_buffer + 0x20 + (offset % ctx->base_storage.sector_size), buffer, count);
if (!is_empty(buffer, count)) {
save_ivfc_storage_do_hash(ctx, hash, data_buffer, ctx->base_storage.sector_size);
}
if (substorage_write(&ctx->base_storage.base_storage, data_buffer + 0x20, offset - (offset % ctx->base_storage.sector_size), ctx->base_storage.sector_size) != ctx->base_storage.sector_size) {
free(data_buffer);
return false;
}
free(data_buffer);
if (substorage_write(&ctx->hash_storage, hash, hash_pos, sizeof(hash)) != sizeof(hash))
return false;
ctx->block_validities[block_index] = VALIDITY_UNCHECKED;
return true;
}

View File

@ -0,0 +1,60 @@
/*
* Copyright (c) 2019-2020 shchmue
*
* 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/>.
*/
/*
ISC License
hactool Copyright (c) 2018, SciresM
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef _IVFC_H_
#define _IVFC_H_
#include "storage.h"
#include <stdint.h>
typedef struct {
substorage hash_storage;
int integrity_check_level;
validity_t *block_validities;
uint8_t salt[0x20];
sector_storage base_storage;
} integrity_verification_storage_ctx_t;
typedef struct {
substorage data;
uint32_t block_size;
uint8_t salt[0x20];
} integrity_verification_info_ctx_t;
void save_ivfc_storage_init(integrity_verification_storage_ctx_t *ctx, integrity_verification_info_ctx_t *info, substorage *hash_storage, int integrity_check_level);
bool save_ivfc_storage_read(integrity_verification_storage_ctx_t *ctx, void *buffer, uint64_t offset, uint64_t count);
bool save_ivfc_storage_write(integrity_verification_storage_ctx_t *ctx, const void *buffer, uint64_t offset, uint64_t count);
#endif

View File

@ -0,0 +1,63 @@
/*
* Copyright (c) 2019-2020 shchmue
*
* 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/>.
*/
/*
ISC License
hactool Copyright (c) 2018, SciresM
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "journal_map.h"
#include "journal_storage.h"
#include "storage.h"
#include <gfx_utils.h>
#include <mem/heap.h>
static journal_map_entry_t *read_map_entries(uint8_t *map_table, uint32_t count) {
journal_map_entry_t *reader = (journal_map_entry_t *)map_table;
journal_map_entry_t *map = malloc(count * sizeof(journal_map_entry_t));
for (uint32_t i = 0; i < count; i++) {
map[i].virtual_index = i;
map[i].physical_index = save_journal_map_entry_get_physical_index(reader->physical_index);
reader++;
}
return map;
}
void save_journal_map_init(journal_map_ctx_t *ctx, journal_map_header_t *header, journal_map_params_t *map_info) {
ctx->header = header;
ctx->map_storage = map_info->map_storage;
ctx->modified_physical_blocks = map_info->physical_block_bitmap;
ctx->modified_virtual_blocks = map_info->virtual_block_bitmap;
ctx->free_blocks = map_info->free_block_bitmap;
ctx->entries = read_map_entries(ctx->map_storage, header->main_data_block_count);
}

View File

@ -0,0 +1,84 @@
/*
* Copyright (c) 2019-2020 shchmue
*
* 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/>.
*/
/*
ISC License
hactool Copyright (c) 2018, SciresM
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef _JOURNAL_MAP_H_
#define _JOURNAL_MAP_H_
#include "storage.h"
#include <utils/types.h>
#include <stdint.h>
#define JOURNAL_MAP_ENTRY_SIZE 8
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 {
uint8_t *map_storage;
uint8_t *physical_block_bitmap;
uint8_t *virtual_block_bitmap;
uint8_t *free_block_bitmap;
} journal_map_params_t;
typedef struct {
uint32_t physical_index;
uint32_t virtual_index;
} journal_map_entry_t;
typedef struct {
journal_map_header_t *header;
journal_map_entry_t *entries;
uint8_t *map_storage;
uint8_t *modified_physical_blocks;
uint8_t *modified_virtual_blocks;
uint8_t *free_blocks;
} journal_map_ctx_t;
static ALWAYS_INLINE uint32_t save_journal_map_entry_make_physical_index(uint32_t index) {
return index | 0x80000000;
}
static ALWAYS_INLINE uint32_t save_journal_map_entry_get_physical_index(uint32_t index) {
return index & 0x7FFFFFFF;
}
void save_journal_map_init(journal_map_ctx_t *ctx, journal_map_header_t *header, journal_map_params_t *map_info);
#endif

View File

@ -0,0 +1,92 @@
/*
* Copyright (c) 2019-2020 shchmue
*
* 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/>.
*/
/*
ISC License
hactool Copyright (c) 2018, SciresM
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "journal_storage.h"
#include "header.h"
#include <gfx_utils.h>
#include <mem/heap.h>
#include <string.h>
void save_journal_storage_init(journal_storage_ctx_t *ctx, substorage *base_storage, journal_header_t *header, journal_map_params_t *map_info) {
memcpy(&ctx->base_storage, base_storage, sizeof(substorage));
ctx->header = header;
save_journal_map_init(&ctx->map, &header->map_header, map_info);
ctx->block_size = (uint32_t)header->block_size;
ctx->length = header->total_size - header->journal_size;
}
uint32_t save_journal_storage_read(journal_storage_ctx_t *ctx, void *buffer, uint64_t offset, uint64_t count) {
uint64_t in_pos = offset;
uint32_t out_pos = 0;
uint32_t remaining = count;
while (remaining) {
uint32_t block_num = (uint32_t)(in_pos / ctx->block_size);
uint32_t block_pos = (uint32_t)(in_pos % ctx->block_size);
uint64_t physical_offset = ctx->map.entries[block_num].physical_index * ctx->block_size + block_pos;
uint32_t bytes_to_read = MIN(ctx->block_size - block_pos, remaining);
if (substorage_read(&ctx->base_storage, (uint8_t *)buffer + out_pos, physical_offset, bytes_to_read) != bytes_to_read)
return 0;
out_pos += bytes_to_read;
in_pos += bytes_to_read;
remaining -= bytes_to_read;
}
return out_pos;
}
uint32_t save_journal_storage_write(journal_storage_ctx_t *ctx, const void *buffer, uint64_t offset, uint64_t count) {
uint64_t in_pos = offset;
uint32_t out_pos = 0;
uint32_t remaining = count;
while (remaining) {
uint32_t block_num = (uint32_t)(in_pos / ctx->block_size);
uint32_t block_pos = (uint32_t)(in_pos % ctx->block_size);
uint64_t physical_offset = ctx->map.entries[block_num].physical_index * ctx->block_size + block_pos;
uint32_t bytes_to_write = MIN(ctx->block_size - block_pos, remaining);
if (substorage_write(&ctx->base_storage, (uint8_t *)buffer + out_pos, physical_offset, bytes_to_write) != bytes_to_write)
return 0;
out_pos += bytes_to_write;
in_pos += bytes_to_write;
remaining -= bytes_to_write;
}
return out_pos;
}

View File

@ -0,0 +1,69 @@
/*
* Copyright (c) 2019-2020 shchmue
*
* 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/>.
*/
/*
ISC License
hactool Copyright (c) 2018, SciresM
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef _JOURNAL_STORAGE_H_
#define _JOURNAL_STORAGE_H_
#include "hierarchical_integrity_verification_storage.h"
#include "journal_map.h"
#include "storage.h"
#include <assert.h>
#include <stdint.h>
typedef struct {
uint32_t magic; /* JNGL */
uint32_t version;
uint64_t total_size;
uint64_t journal_size;
uint64_t block_size;
journal_map_header_t map_header;
uint8_t reserved[0x1D0];
} journal_header_t;
static_assert(sizeof(journal_header_t) == 0x200, "Journal storage header size is wrong!");
typedef struct {
journal_map_ctx_t map;
journal_header_t *header;
uint32_t block_size;
uint64_t length;
substorage base_storage;
} journal_storage_ctx_t;
void save_journal_storage_init(journal_storage_ctx_t *ctx, substorage *base_storage, journal_header_t *header, journal_map_params_t *map_info);
uint32_t save_journal_storage_read(journal_storage_ctx_t *ctx, void *buffer, uint64_t offset, uint64_t count);
uint32_t save_journal_storage_write(journal_storage_ctx_t *ctx, const void *buffer, uint64_t offset, uint64_t count);
#endif

View File

@ -0,0 +1,84 @@
/*
* Copyright (c) 2019-2020 shchmue
*
* 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/>.
*/
/*
ISC License
hactool Copyright (c) 2018, SciresM
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "path_parser.h"
#include <gfx_utils.h>
#include <string.h>
bool save_path_parser_init(path_parser_ctx_t *ctx, const char *path) {
ctx->path_len = strlen(path);
if (ctx->path_len < 1 || path[0] != '/') {
EPRINTF("Path must begin with a '/'!");
return false;
}
ctx->_path = path;
ctx->_offset = 0;
ctx->_length = 0;
ctx->_finished = ctx->path_len == 1 || path[1] == '\0';
return true;
}
bool save_path_parser_move_next(path_parser_ctx_t *ctx) {
if (ctx->_finished)
return false;
ctx->_offset = ctx->_offset + ctx->_length + 1;
uint32_t end = ctx->_offset;
while (end < ctx->path_len && ctx->_path[end] != '\0' && ctx->_path[end] != '/')
end++;
ctx->_finished = end + 1 >= ctx->path_len || ctx->_path[end + 1] == '\0';
ctx->_length = end - ctx->_offset;
return true;
}
const char *save_path_parser_get_current(path_parser_ctx_t *ctx, uint32_t *out_len) {
if (out_len)
*out_len = ctx->_length;
return &ctx->_path[ctx->_offset];
}
bool save_path_parser_try_get_next(path_parser_ctx_t *ctx, char *name) {
bool success = save_path_parser_move_next(ctx);
uint32_t current_len = 0;
const char *current = save_path_parser_get_current(ctx, &current_len);
memcpy(name, current, current_len);
return success;
}

View File

@ -0,0 +1,59 @@
/*
* Copyright (c) 2019-2020 shchmue
*
* 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/>.
*/
/*
ISC License
hactool Copyright (c) 2018, SciresM
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef _PATH_PARSER_H_
#define _PATH_PARSER_H_
#include <utils/types.h>
#include <stdint.h>
typedef struct {
const char *_path;
uint64_t path_len;
uint32_t _offset;
uint32_t _length;
bool _finished;
} path_parser_ctx_t;
static ALWAYS_INLINE bool save_path_parser_is_finished(path_parser_ctx_t *ctx) {
return ctx->_finished;
}
bool save_path_parser_init(path_parser_ctx_t *ctx, const char *path);
bool save_path_parser_move_next(path_parser_ctx_t *ctx);
const char *save_path_parser_get_current(path_parser_ctx_t *ctx, uint32_t *out_len);
bool save_path_parser_try_get_next(path_parser_ctx_t *ctx, char *name);
#endif

View File

@ -0,0 +1,160 @@
/*
* Copyright (c) 2019-2020 shchmue
*
* 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/>.
*/
/*
ISC License
hactool Copyright (c) 2018, SciresM
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "remap_storage.h"
#include "header.h"
#include <gfx_utils.h>
#include <mem/heap.h>
#include <utils/types.h>
#include <string.h>
remap_segment_ctx_t *save_remap_storage_init_segments(remap_storage_ctx_t *ctx) {
remap_header_t *header = ctx->header;
remap_entry_ctx_t *map_entries = ctx->map_entries;
remap_segment_ctx_t *segments = calloc(1, sizeof(remap_segment_ctx_t) * header->map_segment_count);
unsigned int entry_idx = 0;
for (unsigned int i = 0; i < header->map_segment_count; i++) {
remap_segment_ctx_t *seg = &segments[i];
seg->entry_count = 0;
remap_entry_ctx_t **ptr = malloc(sizeof(remap_entry_ctx_t *) * (seg->entry_count + 1));
if (!ptr) {
EPRINTF("Failed to allocate entries in remap storage!");
return NULL;
}
seg->entries = ptr;
seg->entries[seg->entry_count++] = &map_entries[entry_idx];
seg->offset = map_entries[entry_idx].entry.virtual_offset;
map_entries[entry_idx++].segment = seg;
while (entry_idx < header->map_entry_count && map_entries[entry_idx - 1].ends.virtual_offset_end == map_entries[entry_idx].entry.virtual_offset) {
map_entries[entry_idx].segment = seg;
map_entries[entry_idx - 1].next = &map_entries[entry_idx];
remap_entry_ctx_t **ptr = calloc(1, sizeof(remap_entry_ctx_t *) * (seg->entry_count + 1));
if (!ptr) {
EPRINTF("Failed to allocate entries in remap storage!");
return NULL;
}
memcpy(ptr, seg->entries, sizeof(remap_entry_ctx_t *) * (seg->entry_count));
free(seg->entries);
seg->entries = ptr;
seg->entries[seg->entry_count++] = &map_entries[entry_idx++];
}
seg->length = seg->entries[seg->entry_count - 1]->ends.virtual_offset_end - seg->entries[0]->entry.virtual_offset;
}
return segments;
}
static ALWAYS_INLINE remap_entry_ctx_t *save_remap_storage_get_map_entry(remap_storage_ctx_t *ctx, uint64_t offset) {
uint32_t segment_idx = save_remap_get_segment_from_virtual_offset(ctx->header, offset);
if (segment_idx < ctx->header->map_segment_count) {
for (unsigned int i = 0; i < ctx->segments[segment_idx].entry_count; i++) {
if (ctx->segments[segment_idx].entries[i]->ends.virtual_offset_end > offset) {
return ctx->segments[segment_idx].entries[i];
}
}
}
EPRINTFARGS("Remap offset %08x%08x out of range!", (uint32_t)(offset >> 32), (uint32_t)(offset & 0xFFFFFFFF));
return NULL;
}
uint32_t save_remap_storage_read(remap_storage_ctx_t *ctx, void *buffer, uint64_t offset, uint64_t count) {
remap_entry_ctx_t *entry = NULL;
entry = save_remap_storage_get_map_entry(ctx, offset);
if (!entry) {
EPRINTF("Unexpected failure in remap get entry!");
return 0;
}
uint64_t in_pos = offset;
uint32_t out_pos = 0;
uint32_t remaining = count;
while (remaining) {
uint64_t entry_pos = in_pos - entry->entry.virtual_offset;
uint32_t bytes_to_read = MIN((uint32_t)(entry->ends.virtual_offset_end - in_pos), remaining);
if (substorage_read(&ctx->base_storage, (uint8_t *)buffer + out_pos, entry->entry.physical_offset + entry_pos, bytes_to_read) != bytes_to_read)
return 0;
out_pos += bytes_to_read;
in_pos += bytes_to_read;
remaining -= bytes_to_read;
if (in_pos >= entry->ends.virtual_offset_end) {
if (!entry->next && remaining) {
EPRINTF("Unexpected remap entry chain failure!");
return 0;
}
entry = entry->next;
}
}
return out_pos;
}
uint32_t save_remap_storage_write(remap_storage_ctx_t *ctx, const void *buffer, uint64_t offset, uint64_t count) {
remap_entry_ctx_t *entry = NULL;
entry = save_remap_storage_get_map_entry(ctx, offset);
if (!entry) {
EPRINTF("Unexpected failure in remap get entry!");
return 0;
}
uint64_t in_pos = offset;
uint32_t out_pos = 0;
uint32_t remaining = count;
while (remaining) {
uint64_t entry_pos = in_pos - entry->entry.virtual_offset;
uint32_t bytes_to_write = MIN((uint32_t)(entry->ends.virtual_offset_end - in_pos), remaining);
if (substorage_write(&ctx->base_storage, (uint8_t *)buffer + out_pos, entry->entry.physical_offset + entry_pos, bytes_to_write) != bytes_to_write)
return 0;
out_pos += bytes_to_write;
in_pos += bytes_to_write;
remaining -= bytes_to_write;
if (in_pos >= entry->ends.virtual_offset_end) {
if (!entry->next && remaining) {
EPRINTF("Unexpected remap entry chain failure!");
return 0;
}
entry = entry->next;
}
}
return out_pos;
}

View File

@ -0,0 +1,104 @@
/*
* Copyright (c) 2019-2020 shchmue
*
* 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/>.
*/
/*
ISC License
hactool Copyright (c) 2018, SciresM
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef _REMAP_STORAGE_H_
#define _REMAP_STORAGE_H_
#include "duplex_storage.h"
#include "storage.h"
#include <stdint.h>
#define RMAP_ALIGN_SMALL 0x200
#define RMAP_ALIGN_LARGE 0x4000
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 remap_segment_ctx_t remap_segment_ctx_t;
typedef struct remap_entry_ctx_t remap_entry_ctx_t;
typedef struct {
uint64_t virtual_offset_end;
uint64_t physical_offset_end;
} remap_end_offsets_t;
typedef struct {
uint64_t virtual_offset;
uint64_t physical_offset;
uint64_t size;
uint32_t alignment;
uint32_t _0x1C;
} remap_entry_t;
struct remap_entry_ctx_t {
remap_entry_t entry;
remap_end_offsets_t ends;
remap_segment_ctx_t *segment;
remap_entry_ctx_t *next;
};
struct remap_segment_ctx_t{
uint64_t offset;
uint64_t length;
remap_entry_ctx_t **entries;
uint64_t entry_count;
};
typedef struct {
remap_header_t *header;
remap_entry_ctx_t *map_entries;
remap_segment_ctx_t *segments;
substorage base_storage;
} remap_storage_ctx_t;
static ALWAYS_INLINE uint32_t save_remap_get_segment_from_virtual_offset(remap_header_t *header, uint64_t offset) {
return (uint32_t)(offset >> (64 - header->segment_bits));
}
static ALWAYS_INLINE uint64_t save_remap_get_virtual_offset(remap_header_t *header, uint64_t segment) {
return segment << (64 - header->segment_bits);
}
remap_segment_ctx_t *save_remap_storage_init_segments(remap_storage_ctx_t *ctx);
uint32_t save_remap_storage_read(remap_storage_ctx_t *ctx, void *buffer, uint64_t offset, uint64_t count);
uint32_t save_remap_storage_write(remap_storage_ctx_t *ctx, const void *buffer, uint64_t offset, uint64_t count);
#endif

306
bdk/libs/nx_savedata/save.c Normal file
View File

@ -0,0 +1,306 @@
/*
* Copyright (c) 2019-2020 shchmue
*
* 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/>.
*/
/*
ISC License
hactool Copyright (c) 2018, SciresM
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "save.h"
#include <gfx_utils.h>
#include <mem/heap.h>
#include <rtc/max77620-rtc.h>
#include <sec/se.h>
#include <storage/nx_sd.h>
#include <utils/ini.h>
#include <utils/sprintf.h>
#include <stdlib.h>
#include <string.h>
static void save_init_journal_ivfc_storage(save_ctx_t *ctx, hierarchical_integrity_verification_storage_ctx_t *out_ivfc, int integrity_check_level) {
const uint32_t ivfc_levels = 5;
ivfc_save_hdr_t *ivfc = &ctx->header.data_ivfc_header;
substorage levels[ivfc_levels];
substorage_init(&levels[0], &memory_storage_vt, ctx->data_ivfc_master, 0, ctx->header.layout.ivfc_master_hash_size);
for (unsigned int i = 0; i < ivfc_levels - 2; i++) {
ivfc_level_hdr_t *level = &ivfc->level_hash_info.level_headers[i];
substorage_init(&levels[i + 1], &remap_storage_vt, &ctx->meta_remap_storage, fs_int64_get(&level->logical_offset), fs_int64_get(&level->hash_data_size));
}
ivfc_level_hdr_t *data_level = &ivfc->level_hash_info.level_headers[ivfc_levels - 2];
substorage_init(&levels[ivfc_levels - 1], &journal_storage_vt, &ctx->journal_storage, fs_int64_get(&data_level->logical_offset), fs_int64_get(&data_level->hash_data_size));
save_hierarchical_integrity_verification_storage_init_with_levels(out_ivfc, ivfc, ivfc_levels, levels, integrity_check_level);
}
static void save_init_fat_ivfc_storage(save_ctx_t *ctx, hierarchical_integrity_verification_storage_ctx_t *out_ivfc, int integrity_check_level) {
substorage fat_ivfc_master;
substorage_init(&fat_ivfc_master, &memory_storage_vt, ctx->fat_ivfc_master, 0, ctx->header.layout.ivfc_master_hash_size);
save_hierarchical_integrity_verification_storage_init_for_fat(out_ivfc, &ctx->header.version_5.fat_ivfc_header, &fat_ivfc_master, &ctx->meta_remap_storage.base_storage, integrity_check_level);
}
static validity_t save_filesystem_verify(save_ctx_t *ctx) {
validity_t journal_validity = save_hierarchical_integrity_verification_storage_validate(&ctx->core_data_ivfc_storage);
save_hierarchical_integrity_verification_storage_set_level_validities(&ctx->core_data_ivfc_storage);
if (ctx->header.layout.version < VERSION_DISF_5)
return journal_validity;
validity_t fat_validity = save_hierarchical_integrity_verification_storage_validate(&ctx->fat_ivfc_storage);
save_hierarchical_integrity_verification_storage_set_level_validities(&ctx->core_data_ivfc_storage);
if (journal_validity != VALIDITY_VALID)
return journal_validity;
if (fat_validity != VALIDITY_VALID)
return fat_validity;
return journal_validity;
}
static bool save_process_header(save_ctx_t *ctx) {
if (ctx->header.layout.magic != MAGIC_DISF || ctx->header.duplex_header.magic != MAGIC_DPFS ||
ctx->header.data_ivfc_header.magic != MAGIC_IVFC || ctx->header.journal_header.magic != MAGIC_JNGL ||
ctx->header.save_header.magic != MAGIC_SAVE || ctx->header.main_remap_header.magic != MAGIC_RMAP ||
ctx->header.meta_remap_header.magic != MAGIC_RMAP)
{
EPRINTF("Error: Save header is corrupt!");
return false;
}
ctx->data_ivfc_master = (uint8_t *)&ctx->header + ctx->header.layout.ivfc_master_hash_offset_a;
ctx->fat_ivfc_master = (uint8_t *)&ctx->header + ctx->header.layout.fat_ivfc_master_hash_a;
uint8_t hash[0x20];
uint32_t hashed_data_offset = sizeof(ctx->header.layout) + sizeof(ctx->header.cmac) + sizeof(ctx->header._0x10);
uint32_t hashed_data_size = sizeof(ctx->header) - hashed_data_offset;
se_calc_sha256(hash, (uint8_t *)&ctx->header + hashed_data_offset, hashed_data_size);
ctx->header_hash_validity = memcmp(hash, ctx->header.layout.hash, 0x20) == 0 ? VALIDITY_VALID : VALIDITY_INVALID;
unsigned char cmac[0x10] = {};
se_aes_key_set(10, ctx->save_mac_key, 0x10);
se_aes_cmac(10, cmac, 0x10, &ctx->header.layout, sizeof(ctx->header.layout));
if (memcmp(cmac, &ctx->header.cmac, 0x10) == 0) {
ctx->header_cmac_validity = VALIDITY_VALID;
} else {
ctx->header_cmac_validity = VALIDITY_INVALID;
}
return true;
}
void save_init(save_ctx_t *ctx, FIL *file, const uint8_t *save_mac_key, uint32_t action) {
ctx->file = file;
ctx->action = action;
memcpy(ctx->save_mac_key, save_mac_key, sizeof(ctx->save_mac_key));
}
bool save_process(save_ctx_t *ctx) {
substorage_init(&ctx->base_storage, &file_storage_vt, ctx->file, 0, f_size(ctx->file));
/* Try to parse Header A. */
if (substorage_read(&ctx->base_storage, &ctx->header, 0, sizeof(ctx->header)) != sizeof(ctx->header)) {
EPRINTF("Failed to read save header!\n");
return false;
}
if (!save_process_header(ctx) || (ctx->header_hash_validity == VALIDITY_INVALID)) {
/* Try to parse Header B. */
if (substorage_read(&ctx->base_storage, &ctx->header, sizeof(ctx->header), sizeof(ctx->header)) != sizeof(ctx->header)) {
EPRINTF("Failed to read save header!\n");
return false;
}
if (!save_process_header(ctx) || (ctx->header_hash_validity == VALIDITY_INVALID)) {
EPRINTF("Error: Save header is invalid!");
return false;
}
}
/* Initialize remap storages. */
ctx->data_remap_storage.header = &ctx->header.main_remap_header;
ctx->meta_remap_storage.header = &ctx->header.meta_remap_header;
substorage_init(&ctx->data_remap_storage.base_storage, &file_storage_vt, ctx->file, ctx->header.layout.file_map_data_offset, ctx->header.layout.file_map_data_size);
ctx->data_remap_storage.map_entries = calloc(1, sizeof(remap_entry_ctx_t) * ctx->data_remap_storage.header->map_entry_count);
uint8_t *remap_buffer = malloc(MAX(ctx->data_remap_storage.header->map_entry_count, ctx->meta_remap_storage.header->map_entry_count) * sizeof(remap_entry_t));
if (substorage_read(&ctx->base_storage, remap_buffer, ctx->header.layout.file_map_entry_offset, sizeof(remap_entry_t) * ctx->data_remap_storage.header->map_entry_count) != sizeof(remap_entry_t) * ctx->data_remap_storage.header->map_entry_count) {
EPRINTF("Failed to read data remap table!");
free(remap_buffer);
return false;
}
for (unsigned int i = 0; i < ctx->data_remap_storage.header->map_entry_count; i++) {
memcpy(&ctx->data_remap_storage.map_entries[i], remap_buffer + sizeof(remap_entry_t) * i, sizeof(remap_entry_t));
ctx->data_remap_storage.map_entries[i].ends.physical_offset_end = ctx->data_remap_storage.map_entries[i].entry.physical_offset + ctx->data_remap_storage.map_entries[i].entry.size;
ctx->data_remap_storage.map_entries[i].ends.virtual_offset_end = ctx->data_remap_storage.map_entries[i].entry.virtual_offset + ctx->data_remap_storage.map_entries[i].entry.size;
}
/* Initialize data remap storage. */
ctx->data_remap_storage.segments = save_remap_storage_init_segments(&ctx->data_remap_storage);
if (!ctx->data_remap_storage.segments) {
free(remap_buffer);
return false;
}
/* Initialize hierarchical duplex storage. */
if (!save_hierarchical_duplex_storage_init(&ctx->duplex_storage, &ctx->data_remap_storage, &ctx->header)) {
free(remap_buffer);
return false;
}
/* Initialize meta remap storage. */
substorage_init(&ctx->meta_remap_storage.base_storage, &hierarchical_duplex_storage_vt, &ctx->duplex_storage, 0, ctx->duplex_storage.data_layer->_length);
ctx->meta_remap_storage.map_entries = calloc(1, sizeof(remap_entry_ctx_t) * ctx->meta_remap_storage.header->map_entry_count);
if (substorage_read(&ctx->base_storage, remap_buffer, ctx->header.layout.meta_map_entry_offset, sizeof(remap_entry_t) * ctx->meta_remap_storage.header->map_entry_count) != sizeof(remap_entry_t) * ctx->meta_remap_storage.header->map_entry_count) {
EPRINTF("Failed to read meta remap table!");
free(remap_buffer);
return false;
}
for (unsigned int i = 0; i < ctx->meta_remap_storage.header->map_entry_count; i++) {
memcpy(&ctx->meta_remap_storage.map_entries[i], remap_buffer + sizeof(remap_entry_t) * i, sizeof(remap_entry_t));
ctx->meta_remap_storage.map_entries[i].ends.physical_offset_end = ctx->meta_remap_storage.map_entries[i].entry.physical_offset + ctx->meta_remap_storage.map_entries[i].entry.size;
ctx->meta_remap_storage.map_entries[i].ends.virtual_offset_end = ctx->meta_remap_storage.map_entries[i].entry.virtual_offset + ctx->meta_remap_storage.map_entries[i].entry.size;
}
free(remap_buffer);
ctx->meta_remap_storage.segments = save_remap_storage_init_segments(&ctx->meta_remap_storage);
if (!ctx->meta_remap_storage.segments)
return false;
/* Initialize journal map. */
journal_map_params_t journal_map_info;
journal_map_info.map_storage = malloc(ctx->header.layout.journal_map_table_size);
if (save_remap_storage_read(&ctx->meta_remap_storage, journal_map_info.map_storage, ctx->header.layout.journal_map_table_offset, ctx->header.layout.journal_map_table_size) != ctx->header.layout.journal_map_table_size) {
EPRINTF("Failed to read journal map!");
return false;
}
/* Initialize journal storage. */
substorage journal_data;
substorage_init(&journal_data, &remap_storage_vt, &ctx->data_remap_storage, ctx->header.layout.journal_data_offset, ctx->header.layout.journal_data_size_b + ctx->header.layout.journal_size);
save_journal_storage_init(&ctx->journal_storage, &journal_data, &ctx->header.journal_header, &journal_map_info);
/* Initialize core IVFC storage. */
save_init_journal_ivfc_storage(ctx, &ctx->core_data_ivfc_storage, ctx->action & ACTION_VERIFY);
/* Initialize FAT storage. */
if (ctx->header.layout.version < VERSION_DISF_5) {
ctx->fat_storage = malloc(ctx->header.layout.fat_size);
save_remap_storage_read(&ctx->meta_remap_storage, ctx->fat_storage, ctx->header.layout.fat_offset, ctx->header.layout.fat_size);
} else {
save_init_fat_ivfc_storage(ctx, &ctx->fat_ivfc_storage, ctx->action & ACTION_VERIFY);
ctx->fat_storage = malloc(ctx->fat_ivfc_storage.length);
save_remap_storage_read(&ctx->meta_remap_storage, ctx->fat_storage, fs_int64_get(&ctx->header.version_5.fat_ivfc_header.level_hash_info.level_headers[2].logical_offset), ctx->fat_ivfc_storage.length);
}
if (ctx->action & ACTION_VERIFY) {
save_filesystem_verify(ctx);
}
/* Initialize core save filesystem. */
save_data_file_system_core_init(&ctx->save_filesystem_core, &ctx->core_data_ivfc_storage.base_storage, ctx->fat_storage, &ctx->header.save_header);
return true;
}
void save_free_contexts(save_ctx_t *ctx) {
for (unsigned int i = 0; i < ctx->data_remap_storage.header->map_segment_count; i++) {
free(ctx->data_remap_storage.segments[i].entries);
}
free(ctx->data_remap_storage.segments);
for (unsigned int i = 0; i < ctx->meta_remap_storage.header->map_segment_count; i++) {
free(ctx->meta_remap_storage.segments[i].entries);
}
free(ctx->meta_remap_storage.segments);
free(ctx->data_remap_storage.map_entries);
free(ctx->meta_remap_storage.map_entries);
for (unsigned int i = 0; i < 2; i++) {
free(ctx->duplex_storage.layers[i].bitmap.bitmap);
free(ctx->duplex_storage.layers[i].data_a.base_storage.ctx);
free(ctx->duplex_storage.layers[i].data_b.base_storage.ctx);
}
free(ctx->duplex_storage.layers[1].bitmap_storage.base_storage.ctx);
free(ctx->journal_storage.map.map_storage);
free(ctx->journal_storage.map.entries);
for (unsigned int i = 0; i < 4; i++) {
free(ctx->core_data_ivfc_storage.integrity_storages[i].block_validities);
save_cached_storage_finalize(&ctx->core_data_ivfc_storage.levels[i + 1]);
}
free(ctx->core_data_ivfc_storage.level_validities);
if (ctx->header.layout.version >= VERSION_DISF_5) {
for (unsigned int i = 0; i < 3; i++) {
free(ctx->fat_ivfc_storage.integrity_storages[i].block_validities);
save_cached_storage_finalize(&ctx->fat_ivfc_storage.levels[i + 1]);
}
}
free(ctx->fat_ivfc_storage.level_validities);
free(ctx->fat_storage);
}
static ALWAYS_INLINE bool save_flush(save_ctx_t *ctx) {
if (ctx->header.layout.version < VERSION_DISF_5) {
if (!save_cached_storage_flush(ctx->core_data_ivfc_storage.data_level)) {
EPRINTF("Failed to flush cached storage!");
}
if (save_remap_storage_write(&ctx->meta_remap_storage, ctx->fat_storage, ctx->header.layout.fat_offset, ctx->header.layout.fat_size) != ctx->header.layout.fat_size) {
EPRINTF("Failed to write meta remap storage!");
}
} else {
if (!save_cached_storage_flush(ctx->fat_ivfc_storage.data_level)) {
EPRINTF("Failed to flush cached storage!");
}
if (save_remap_storage_write(&ctx->meta_remap_storage, ctx->fat_storage, fs_int64_get(&ctx->header.version_5.fat_ivfc_header.level_hash_info.level_headers[2].logical_offset), ctx->fat_ivfc_storage.length) != ctx->fat_ivfc_storage.length) {
EPRINTF("Failed to write meta remap storage!");
}
}
return save_hierarchical_duplex_storage_flush(&ctx->duplex_storage, &ctx->data_remap_storage, &ctx->header);
}
bool save_commit(save_ctx_t *ctx) {
if (!save_flush(ctx)) {
EPRINTF("Failed to flush save!");
return false;
}
uint32_t hashed_data_offset = sizeof(ctx->header.layout) + sizeof(ctx->header.cmac) + sizeof(ctx->header._0x10);
uint32_t hashed_data_size = sizeof(ctx->header) - hashed_data_offset;
uint8_t *header = (uint8_t *)&ctx->header;
se_calc_sha256(ctx->header.layout.hash, header + hashed_data_offset, hashed_data_size);
se_aes_key_set(10, ctx->save_mac_key, 0x10);
se_aes_cmac(10, ctx->header.cmac, 0x10, &ctx->header.layout, sizeof(ctx->header.layout));
if (substorage_write(&ctx->base_storage, &ctx->header, 0, sizeof(ctx->header)) != sizeof(ctx->header)) {
EPRINTF("Failed to write save header!");
return false;
}
return true;
}

154
bdk/libs/nx_savedata/save.h Normal file
View File

@ -0,0 +1,154 @@
/*
* Copyright (c) 2019-2020 shchmue
*
* 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/>.
*/
/*
ISC License
hactool Copyright (c) 2018, SciresM
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef _SAVE_H_
#define _SAVE_H_
#include "header.h"
#include "hierarchical_duplex_storage.h"
#include "hierarchical_integrity_verification_storage.h"
#include "journal_map.h"
#include "journal_storage.h"
#include "remap_storage.h"
#include "save_data_file_system_core.h"
#include "storage.h"
#include <libs/fatfs/ff.h>
#include <utils/types.h>
#include <assert.h>
#include <stdint.h>
#define ACTION_VERIFY (1<<2)
typedef struct {
save_header_t header;
FIL *file;
uint32_t action;
validity_t header_cmac_validity;
validity_t header_hash_validity;
uint8_t *data_ivfc_master;
uint8_t *fat_ivfc_master;
remap_storage_ctx_t data_remap_storage;
remap_storage_ctx_t meta_remap_storage;
hierarchical_duplex_storage_ctx_t duplex_storage;
journal_storage_ctx_t journal_storage;
hierarchical_integrity_verification_storage_ctx_t core_data_ivfc_storage;
hierarchical_integrity_verification_storage_ctx_t fat_ivfc_storage;
uint8_t *fat_storage;
save_data_file_system_core_ctx_t save_filesystem_core;
substorage base_storage;
uint8_t save_mac_key[0x10];
} save_ctx_t;
typedef enum {
SPACE_ID_SYSTEM = 0,
SPACE_ID_USER = 1,
SPACE_ID_SD_SYSTEM = 2,
SPACE_ID_TEMP = 3,
SPACE_ID_SD_USER = 4,
SPACE_ID_PROPER_SYSTEM = 100,
SPACE_ID_SAFE_MODE = 101,
} save_data_space_id_t;
typedef struct {
int64_t save_data_size;
int64_t journal_size;
int64_t block_size;
uint64_t owner_id;
uint32_t flags;
uint8_t space_id;
uint8_t pseudo;
uint8_t reserved[0x1A];
} save_data_creation_info_t;
static_assert(sizeof(save_data_creation_info_t) == 0x40, "Save data creation info size is wrong!");
static ALWAYS_INLINE uint32_t save_calc_map_entry_storage_size(int32_t entry_count) {
int32_t val = entry_count < 1 ? entry_count : entry_count - 1;
return (entry_count + (val >> 1)) * sizeof(remap_entry_t);
}
void save_init(save_ctx_t *ctx, FIL *file, const uint8_t *save_mac_key, uint32_t action);
bool save_process(save_ctx_t *ctx);
bool save_create(save_ctx_t *ctx, uint32_t version, const char *mount_path, const save_data_attribute_t *attr, const save_data_creation_info_t *creation_info);
bool save_create_system_save_data(save_ctx_t *ctx, uint32_t version, const char *mount_path, uint8_t space_id, uint64_t save_id, const account_user_id_t *user_id, uint64_t owner_id, uint64_t save_data_size, uint64_t journal_size, uint32_t flags);
void save_free_contexts(save_ctx_t *ctx);
bool save_commit(save_ctx_t *ctx);
static ALWAYS_INLINE bool save_create_directory(save_ctx_t *ctx, const char *path) {
return save_data_file_system_core_create_directory(&ctx->save_filesystem_core, path);
}
static ALWAYS_INLINE bool save_create_file(save_ctx_t *ctx, const char *path, uint64_t size) {
return save_data_file_system_core_create_file(&ctx->save_filesystem_core, path, size);
}
static ALWAYS_INLINE bool save_delete_directory(save_ctx_t *ctx, const char *path) {
return save_data_file_system_core_delete_directory(&ctx->save_filesystem_core,path);
}
static ALWAYS_INLINE bool save_delete_file(save_ctx_t *ctx, const char *path) {
return save_data_file_system_core_delete_file(&ctx->save_filesystem_core, path);
}
static ALWAYS_INLINE bool save_open_directory(save_ctx_t *ctx, save_data_directory_ctx_t *directory, const char *path, open_directory_mode_t mode) {
return save_data_file_system_core_open_directory(&ctx->save_filesystem_core, directory, path, mode);
}
static ALWAYS_INLINE bool save_open_file(save_ctx_t *ctx, save_data_file_ctx_t *file, const char *path, open_mode_t mode) {
return save_data_file_system_core_open_file(&ctx->save_filesystem_core, file, path, mode);
}
static ALWAYS_INLINE bool save_rename_directory(save_ctx_t *ctx, const char *old_path, const char *new_path) {
return save_data_file_system_core_rename_directory(&ctx->save_filesystem_core, old_path, new_path);
}
static ALWAYS_INLINE bool save_rename_file(save_ctx_t *ctx, const char *old_path, const char *new_path) {
return save_data_file_system_core_rename_file(&ctx->save_filesystem_core, old_path, new_path);
}
static ALWAYS_INLINE bool save_get_entry_type(save_ctx_t *ctx, directory_entry_type_t *out_entry_type, const char *path) {
return save_data_file_system_core_get_entry_type(&ctx->save_filesystem_core, out_entry_type, path);
}
static ALWAYS_INLINE void save_get_free_space_size(save_ctx_t *ctx, uint64_t *out_free_space) {
return save_data_file_system_core_get_free_space_size(&ctx->save_filesystem_core, out_free_space);
}
static ALWAYS_INLINE void save_get_total_space_size(save_ctx_t *ctx, uint64_t *out_total_size) {
return save_data_file_system_core_get_total_space_size(&ctx->save_filesystem_core, out_total_size);
}
#endif

View File

@ -0,0 +1,89 @@
/*
* Copyright (c) 2019-2020 shchmue
*
* 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/>.
*/
/*
ISC License
hactool Copyright (c) 2018, SciresM
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "save_data_directory.h"
#include <string.h>
void save_data_directory_init(save_data_directory_ctx_t *ctx, hierarchical_save_file_table_ctx_t *table, save_find_position_t *position, open_directory_mode_t mode) {
ctx->parent_file_table = table;
ctx->initial_position = position;
ctx->_current_position = position;
ctx->mode = mode;
}
bool save_data_directory_read_impl(save_data_directory_ctx_t *ctx, uint64_t *out_entries_read, save_find_position_t *position, directory_entry_t *entry_buffer, uint64_t entry_count) {
hierarchical_save_file_table_ctx_t *tab = ctx->parent_file_table;
uint32_t i = 0;
save_file_info_t info;
char name[SAVE_FS_LIST_MAX_NAME_LENGTH];
if (ctx->mode & OPEN_DIR_MODE_DIR) {
while ((!entry_buffer || i < entry_count) && save_hierarchical_file_table_find_next_directory(tab, position, name)) {
directory_entry_t *entry = &entry_buffer[i];
memcpy(entry->name, name, SAVE_FS_LIST_MAX_NAME_LENGTH);
entry->name[SAVE_FS_LIST_MAX_NAME_LENGTH] = 0;
entry->type = DIR_ENT_TYPE_DIR;
entry->size = 0;
i++;
}
}
if (ctx->mode & OPEN_DIR_MODE_FILE) {
while ((!entry_buffer || i < entry_count) && save_hierarchical_file_table_find_next_file(tab, position, &info, name)) {
directory_entry_t *entry = &entry_buffer[i];
memcpy(entry->name, name, SAVE_FS_LIST_MAX_NAME_LENGTH);
entry->name[SAVE_FS_LIST_MAX_NAME_LENGTH] = 0;
entry->type = DIR_ENT_TYPE_FILE;
entry->size = 0;
i++;
}
}
*out_entries_read = i;
return true;
}
bool save_data_directory_read(save_data_directory_ctx_t *ctx, uint64_t *out_entries_read, directory_entry_t *entry_buffer, uint64_t entry_count) {
return save_data_directory_read_impl(ctx, out_entries_read, ctx->_current_position, entry_buffer, entry_count);
}
bool save_data_directory_get_entry_count(save_data_directory_ctx_t *ctx, uint64_t *out_entry_count) {
return save_data_directory_read_impl(ctx, out_entry_count, ctx->initial_position, NULL, 0);
}

View File

@ -0,0 +1,56 @@
/*
* Copyright (c) 2019-2020 shchmue
*
* 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/>.
*/
/*
ISC License
hactool Copyright (c) 2018, SciresM
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef _SAVE_DATA_DIRECTORY_H_
#define _SAVE_DATA_DIRECTORY_H_
#include "directory_entry.h"
#include "hierarchical_save_file_table.h"
#include "save_data_directory.h"
#include "save_fs_entry.h"
#include <stdint.h>
typedef struct {
hierarchical_save_file_table_ctx_t *parent_file_table;
open_directory_mode_t mode;
save_find_position_t *initial_position;
save_find_position_t *_current_position;
} save_data_directory_ctx_t;
void save_data_directory_init(save_data_directory_ctx_t *ctx, hierarchical_save_file_table_ctx_t *table, save_find_position_t *position, open_directory_mode_t mode);
bool save_data_directory_read(save_data_directory_ctx_t *ctx, uint64_t *out_entries_read, directory_entry_t *entry_buffer, uint64_t entry_count);
bool save_data_directory_get_entry_count(save_data_directory_ctx_t *ctx, uint64_t *out_entry_count);
#endif

View File

@ -0,0 +1,132 @@
/*
* Copyright (c) 2019-2020 shchmue
*
* 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/>.
*/
/*
ISC License
hactool Copyright (c) 2018, SciresM
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "save_data_file.h"
#include <gfx_utils.h>
void save_data_file_init(save_data_file_ctx_t *ctx, allocation_table_storage_ctx_t *base_storage, const char *path, hierarchical_save_file_table_ctx_t *file_table, uint64_t size, open_mode_t mode) {
ctx->mode = mode;
memcpy(&ctx->base_storage, base_storage, sizeof(ctx->base_storage));
ctx->path = path;
ctx->file_table = file_table;
ctx->size = size;
}
bool save_data_file_validate_read_params(save_data_file_ctx_t *ctx, uint64_t *out_bytes_to_read, uint64_t offset, uint32_t size, open_mode_t open_mode) {
*out_bytes_to_read = 0;
if ((open_mode & OPEN_MODE_READ) == 0)
return false;
uint64_t file_size = ctx->size;
if (offset > file_size)
return false;
*out_bytes_to_read = MIN(file_size - offset, size);
return true;
}
bool save_data_file_validate_write_params(save_data_file_ctx_t *ctx, uint64_t offset, uint32_t size, open_mode_t open_mode, bool *out_is_resize_needed) {
*out_is_resize_needed = false;
if ((open_mode & OPEN_MODE_WRITE) == 0)
return false;
uint64_t file_size = ctx->size;
if (offset + size > file_size) {
*out_is_resize_needed = true;
if ((open_mode & OPEN_MODE_ALLOW_APPEND) == 0)
return false;
}
return true;
}
bool save_data_file_read(save_data_file_ctx_t *ctx, uint64_t *out_bytes_read, uint64_t offset, void *buffer, uint64_t count) {
uint64_t to_read = 0;
if (!save_data_file_validate_read_params(ctx, &to_read, offset, count, ctx->mode))
return false;
if (to_read == 0) {
*out_bytes_read = 0;
return true;
}
*out_bytes_read = save_allocation_table_storage_read(&ctx->base_storage, buffer, offset, to_read);
return true;
}
bool save_data_file_write(save_data_file_ctx_t *ctx, uint64_t *out_bytes_written, uint64_t offset, const void *buffer, uint64_t count) {
bool is_resize_needed;
if (!save_data_file_validate_write_params(ctx, offset, count, ctx->mode, &is_resize_needed))
return false;
if (is_resize_needed) {
if (!save_data_file_set_size(ctx, offset + count))
return false;
}
*out_bytes_written = save_allocation_table_storage_write(&ctx->base_storage, buffer, offset, count);
return true;
}
bool save_data_file_set_size(save_data_file_ctx_t *ctx, uint64_t size) {
if (ctx->size == size)
return true;
save_allocation_table_storage_set_size(&ctx->base_storage, size);
save_file_info_t file_info;
if (!save_hierarchical_file_table_try_open_file(ctx->file_table, ctx->path, &file_info)) {
EPRINTF("File not found!");
return false;
}
file_info.start_block = ctx->base_storage.initial_block;
fs_int64_set(&file_info.length, size);
if (!save_hierarchical_file_table_add_file(ctx->file_table, ctx->path, &file_info))
return false;
ctx->size = size;
return true;
}

View File

@ -0,0 +1,62 @@
/*
* Copyright (c) 2019-2020 shchmue
*
* 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/>.
*/
/*
ISC License
hactool Copyright (c) 2018, SciresM
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef _SAVE_DATA_FILE_H_
#define _SAVE_DATA_FILE_H_
#include "allocation_table_storage.h"
#include "hierarchical_save_file_table.h"
#include <utils/types.h>
#include <stdint.h>
typedef struct {
allocation_table_storage_ctx_t base_storage;
const char *path;
hierarchical_save_file_table_ctx_t *file_table;
uint64_t size;
open_mode_t mode;
} save_data_file_ctx_t;
static ALWAYS_INLINE void save_data_file_get_size(save_data_file_ctx_t *ctx, uint64_t *out_size) {
*out_size = ctx->size;
}
void save_data_file_init(save_data_file_ctx_t *ctx, allocation_table_storage_ctx_t *base_storage, const char *path, hierarchical_save_file_table_ctx_t *file_table, uint64_t size, open_mode_t mode);
bool save_data_file_read(save_data_file_ctx_t *ctx, uint64_t *out_bytes_read, uint64_t offset, void *buffer, uint64_t count);
bool save_data_file_write(save_data_file_ctx_t *ctx, uint64_t *out_bytes_written, uint64_t offset, const void *buffer, uint64_t count);
bool save_data_file_set_size(save_data_file_ctx_t *ctx, uint64_t size);
#endif

View File

@ -0,0 +1,142 @@
/*
* Copyright (c) 2019-2020 shchmue
*
* 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/>.
*/
/*
ISC License
hactool Copyright (c) 2018, SciresM
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "save_data_file_system_core.h"
#include "allocation_table_storage.h"
#include "header.h"
#include "save.h"
#include <gfx_utils.h>
#include <mem/heap.h>
static ALWAYS_INLINE void save_data_file_system_core_open_fat_storage(save_data_file_system_core_ctx_t *ctx, allocation_table_storage_ctx_t *storage_ctx, uint32_t block_index) {
save_allocation_table_storage_init(storage_ctx, ctx->base_storage, &ctx->allocation_table, (uint32_t)ctx->header->block_size, block_index);
}
void save_data_file_system_core_init(save_data_file_system_core_ctx_t *ctx, substorage *storage, void *allocation_table, save_fs_header_t *save_fs_header) {
save_allocation_table_init(&ctx->allocation_table, allocation_table, &save_fs_header->fat_header);
ctx->header = save_fs_header;
ctx->base_storage = storage;
save_filesystem_list_ctx_t *dir_table = &ctx->file_table.directory_table;
save_filesystem_list_ctx_t *file_table = &ctx->file_table.file_table;
save_data_file_system_core_open_fat_storage(ctx, &dir_table->storage, save_fs_header->fat_header.directory_table_block);
save_data_file_system_core_open_fat_storage(ctx, &file_table->storage, save_fs_header->fat_header.file_table_block);
save_fs_list_init(dir_table);
save_fs_list_init(file_table);
}
bool save_data_file_system_core_create_directory(save_data_file_system_core_ctx_t *ctx, const char *path) {
return save_hierarchical_file_table_add_directory(&ctx->file_table, path);
}
bool save_data_file_system_core_create_file(save_data_file_system_core_ctx_t *ctx, const char *path, uint64_t size) {
if (size == 0) {
save_file_info_t empty_file_entry = {0x80000000, {0}, {0}};
save_hierarchical_file_table_add_file(&ctx->file_table, path, &empty_file_entry);
return true;
}
uint32_t block_count = (uint32_t)DIV_ROUND_UP(size, ctx->allocation_table.header->block_size);
uint32_t start_block = save_allocation_table_allocate(&ctx->allocation_table, block_count);
if (start_block == 0xFFFFFFFF)
return false;
save_file_info_t file_entry = {start_block, {0}, {0}};
fs_int64_set(&file_entry.length, size);
return save_hierarchical_file_table_add_file(&ctx->file_table, path, &file_entry);
}
bool save_data_file_system_core_delete_directory(save_data_file_system_core_ctx_t *ctx, const char *path) {
return save_hierarchical_file_table_delete_directory(&ctx->file_table, path);
}
bool save_data_file_system_core_delete_file(save_data_file_system_core_ctx_t *ctx, const char *path) {
save_file_info_t file_info;
if (!save_hierarchical_file_table_try_open_file(&ctx->file_table, path, &file_info))
return false;
if (file_info.start_block != 0x80000000) {
save_allocation_table_free(&ctx->allocation_table, file_info.start_block);
}
save_hierarchical_file_table_delete_file(&ctx->file_table, path);
return true;
}
bool save_data_file_system_core_open_directory(save_data_file_system_core_ctx_t *ctx, save_data_directory_ctx_t *directory, const char *path, open_directory_mode_t mode) {
memset(directory, 0, sizeof(save_data_directory_ctx_t));
save_find_position_t position;
if (!save_hierarchical_file_table_try_open_directory(&ctx->file_table, path, &position))
return false;
save_data_directory_init(directory, &ctx->file_table, &position, mode);
return true;
}
bool save_data_file_system_core_open_file(save_data_file_system_core_ctx_t *ctx, save_data_file_ctx_t *file, const char *path, open_mode_t mode) {
memset(file, 0, sizeof(save_data_file_ctx_t));
save_file_info_t file_info;
if (!save_hierarchical_file_table_try_open_file(&ctx->file_table, path, &file_info))
return false;
allocation_table_storage_ctx_t storage;
save_data_file_system_core_open_fat_storage(ctx, &storage, file_info.start_block);
save_data_file_init(file, &storage, path, &ctx->file_table, fs_int64_get(&file_info.length), mode);
return true;
}
bool save_data_file_system_core_get_entry_type(save_data_file_system_core_ctx_t *ctx, directory_entry_type_t *out_entry_type, const char *path) {
save_file_info_t info;
if (save_hierarchical_file_table_try_open_file(&ctx->file_table, path, &info)) {
*out_entry_type = DIR_ENT_TYPE_FILE;
return true;
}
save_find_position_t position;
if (save_hierarchical_file_table_try_open_directory(&ctx->file_table, path, &position)) {
*out_entry_type = DIR_ENT_TYPE_DIR;
return true;
}
return false;
}

View File

@ -0,0 +1,79 @@
/*
* Copyright (c) 2019-2020 shchmue
*
* 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/>.
*/
/*
ISC License
hactool Copyright (c) 2018, SciresM
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef _SAVE_DATA_FILE_SYSTEM_CORE_H_
#define _SAVE_DATA_FILE_SYSTEM_CORE_H_
#include "allocation_table.h"
#include "header.h"
#include "hierarchical_save_file_table.h"
#include "save_data_directory.h"
#include "save_data_file.h"
#include "storage.h"
typedef struct {
substorage *base_storage;
allocation_table_ctx_t allocation_table;
save_fs_header_t *header;
hierarchical_save_file_table_ctx_t file_table;
} save_data_file_system_core_ctx_t;
static ALWAYS_INLINE bool save_data_file_system_core_rename_directory(save_data_file_system_core_ctx_t *ctx, const char *old_path, const char *new_path) {
return save_hierarchical_file_table_rename_directory(&ctx->file_table, old_path, new_path);
}
static ALWAYS_INLINE bool save_data_file_system_core_rename_file(save_data_file_system_core_ctx_t *ctx, const char *old_path, const char *new_path) {
return save_hierarchical_file_table_rename_file(&ctx->file_table, old_path, new_path);
}
static ALWAYS_INLINE void save_data_file_system_core_get_free_space_size(save_data_file_system_core_ctx_t *ctx, uint64_t *out_free_space) {
uint32_t free_block_count = save_allocation_table_get_free_list_length(&ctx->allocation_table);
*out_free_space = ctx->header->block_size * free_block_count;
}
static ALWAYS_INLINE void save_data_file_system_core_get_total_space_size(save_data_file_system_core_ctx_t *ctx, uint64_t *out_total_space) {
*out_total_space = ctx->header->block_size * ctx->header->block_count;
}
void save_data_file_system_core_init(save_data_file_system_core_ctx_t *ctx, substorage *storage, void *allocation_table, save_fs_header_t *save_fs_header);
bool save_data_file_system_core_create_directory(save_data_file_system_core_ctx_t *ctx, const char *path);
bool save_data_file_system_core_create_file(save_data_file_system_core_ctx_t *ctx, const char *path, uint64_t size);
bool save_data_file_system_core_delete_directory(save_data_file_system_core_ctx_t *ctx, const char *path);
bool save_data_file_system_core_delete_file(save_data_file_system_core_ctx_t *ctx, const char *path);
bool save_data_file_system_core_open_directory(save_data_file_system_core_ctx_t *ctx, save_data_directory_ctx_t *directory, const char *path, open_directory_mode_t mode);
bool save_data_file_system_core_open_file(save_data_file_system_core_ctx_t *ctx, save_data_file_ctx_t *file, const char *path, open_mode_t mode);
bool save_data_file_system_core_get_entry_type(save_data_file_system_core_ctx_t *ctx, directory_entry_type_t *out_entry_type, const char *path);
#endif

View File

@ -0,0 +1,66 @@
/*
* Copyright (c) 2019-2020 shchmue
*
* 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/>.
*/
/*
ISC License
hactool Copyright (c) 2018, SciresM
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef _SAVE_FS_ENTRY_H_
#define _SAVE_FS_ENTRY_H_
#include "fs_int64.h"
#include <assert.h>
#include <stdint.h>
typedef struct {
char name[0x40];
uint32_t parent;
} save_entry_key_t;
static_assert(sizeof(save_entry_key_t) == 0x44, "Save entry key size is wrong!");
typedef struct {
uint32_t start_block;
fs_int64_t length;
uint32_t _0xC[2];
} save_file_info_t;
static_assert(sizeof(save_file_info_t) == 0x14, "Save file info size is wrong!");
typedef struct {
uint32_t next_directory;
uint32_t next_file;
uint32_t _0x8[3];
} save_find_position_t;
static_assert(sizeof(save_find_position_t) == 0x14, "Save find position size is wrong!");
#endif

View File

@ -0,0 +1,300 @@
/*
* Copyright (c) 2019-2020 shchmue
*
* 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/>.
*/
/*
ISC License
hactool Copyright (c) 2018, SciresM
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "save_fs_list.h"
#include <gfx_utils.h>
#include <string.h>
void save_fs_list_init(save_filesystem_list_ctx_t *ctx) {
ctx->free_list_head_index = 0;
ctx->used_list_head_index = 1;
}
static ALWAYS_INLINE uint32_t save_fs_list_get_capacity(save_filesystem_list_ctx_t *ctx) {
uint32_t capacity;
if (save_allocation_table_storage_read(&ctx->storage, &capacity, 4, 4) != 4) {
EPRINTF("Failed to read FS list capacity!");
return 0;
}
return capacity;
}
static ALWAYS_INLINE uint32_t save_fs_list_get_length(save_filesystem_list_ctx_t *ctx) {
uint32_t length;
if (save_allocation_table_storage_read(&ctx->storage, &length, 0, 4) != 4) {
EPRINTF("Failed to read FS list length!");
return 0;
}
return length;
}
static ALWAYS_INLINE bool save_fs_list_set_capacity(save_filesystem_list_ctx_t *ctx, uint32_t capacity) {
return save_allocation_table_storage_write(&ctx->storage, &capacity, 4, 4) == 4;
}
static ALWAYS_INLINE bool save_fs_list_set_length(save_filesystem_list_ctx_t *ctx, uint32_t length) {
return save_allocation_table_storage_write(&ctx->storage, &length, 0, 4) == 4;
}
uint32_t save_fs_list_get_index_from_key(save_filesystem_list_ctx_t *ctx, const save_entry_key_t *key, uint32_t *prev_index) {
save_fs_list_entry_t entry;
uint32_t capacity = save_fs_list_get_capacity(ctx);
if (save_fs_list_read_entry(ctx, ctx->used_list_head_index, &entry) != SAVE_FS_LIST_ENTRY_SIZE) {
EPRINTF("Failed to read used list head entry!");
return 0xFFFFFFFF;
}
uint32_t prev;
if (!prev_index) {
prev_index = &prev;
}
*prev_index = ctx->used_list_head_index;
uint32_t index = entry.next;
while (index) {
if (index > capacity) {
EPRINTFARGS("Save entry index %d out of range!", index);
return 0xFFFFFFFF;
}
if (save_fs_list_read_entry(ctx, index, &entry) != SAVE_FS_LIST_ENTRY_SIZE)
return 0xFFFFFFFF;
if (entry.parent == key->parent && !strcmp(entry.name, key->name)) {
return index;
}
*prev_index = index;
index = entry.next;
}
*prev_index = 0xFFFFFFFF;
return 0xFFFFFFFF;
}
bool save_fs_list_get_value_by_index(save_filesystem_list_ctx_t *ctx, uint32_t index, save_table_entry_t *value) {
save_fs_list_entry_t entry;
memset(&entry, 0, sizeof(entry));
if (save_fs_list_read_entry(ctx, index, &entry) != SAVE_FS_LIST_ENTRY_SIZE) {
EPRINTFARGS("Failed to read FS list entry at index %x!", index);
return false;
}
memcpy(value, &entry.value, sizeof(save_table_entry_t));
return true;
}
bool save_fs_list_get_value_and_name(save_filesystem_list_ctx_t *ctx, uint32_t index, save_table_entry_t *value, char *name) {
save_fs_list_entry_t entry;
memset(&entry, 0, sizeof(entry));
if (save_fs_list_read_entry(ctx, index, &entry) != SAVE_FS_LIST_ENTRY_SIZE) {
EPRINTFARGS("Failed to read FS list entry at index %x!", index);
return false;
}
memcpy(value, &entry.value, sizeof(save_table_entry_t));
memcpy(name, entry.name, SAVE_FS_LIST_MAX_NAME_LENGTH);
return true;
}
bool save_fs_list_try_get_value_by_index(save_filesystem_list_ctx_t *ctx, uint32_t index, save_table_entry_t *value) {
if ((index & 0x80000000) != 0 || index >= save_fs_list_get_capacity(ctx)) {
memset(value, 0, sizeof(save_table_entry_t));
return false;
}
return save_fs_list_get_value_by_index(ctx, index, value);
}
bool save_fs_list_try_get_value_by_key(save_filesystem_list_ctx_t *ctx, save_entry_key_t *key, save_table_entry_t *value) {
uint32_t index = save_fs_list_get_index_from_key(ctx, key, NULL);
if ((index & 0x80000000) != 0) {
memset(value, 0, sizeof(save_table_entry_t));
return false;
}
return save_fs_list_try_get_value_by_index(ctx, index, value);
}
bool save_fs_list_try_get_value_and_name(save_filesystem_list_ctx_t *ctx, uint32_t index, save_table_entry_t *value, char *name) {
if ((index & 0x80000000) != 0 || index >= save_fs_list_get_capacity(ctx)) {
memset(value, 0, sizeof(save_table_entry_t));
return false;
}
return save_fs_list_get_value_and_name(ctx, index, value, name);
}
bool save_fs_list_set_value(save_filesystem_list_ctx_t *ctx, uint32_t index, const save_table_entry_t *value) {
save_fs_list_entry_t entry = {0};
if (save_fs_list_read_entry(ctx, index, &entry) != SAVE_FS_LIST_ENTRY_SIZE)
return false;
memcpy(&entry.value, value, sizeof(save_table_entry_t));
return save_fs_list_write_entry(ctx, index, &entry) == SAVE_FS_LIST_ENTRY_SIZE;
}
bool save_fs_list_free(save_filesystem_list_ctx_t *ctx, uint32_t entry_index) {
save_fs_list_entry_t free_entry, entry;
if (save_fs_list_read_entry(ctx, ctx->free_list_head_index, &free_entry) != SAVE_FS_LIST_ENTRY_SIZE)
return false;
if (save_fs_list_read_entry(ctx, entry_index, &entry) != SAVE_FS_LIST_ENTRY_SIZE)
return false;
entry.next = free_entry.next;
free_entry.next = entry_index;
if (save_fs_list_write_entry(ctx, ctx->free_list_head_index, &free_entry) != SAVE_FS_LIST_ENTRY_SIZE)
return false;
return save_fs_list_write_entry(ctx, entry_index, &entry) == SAVE_FS_LIST_ENTRY_SIZE;
}
bool save_fs_list_remove(save_filesystem_list_ctx_t *ctx, const save_entry_key_t *key) {
uint32_t index, previous_index;
index = save_fs_list_get_index_from_key(ctx, key, &previous_index);
if (index == 0xFFFFFFFF)
return false;
save_fs_list_entry_t prev_entry, entry_to_del;
if (save_fs_list_read_entry(ctx, previous_index, &prev_entry) != SAVE_FS_LIST_ENTRY_SIZE)
return false;
if (save_fs_list_read_entry(ctx, index, &entry_to_del) != SAVE_FS_LIST_ENTRY_SIZE)
return false;
prev_entry.next = entry_to_del.next;
if (save_fs_list_write_entry(ctx, previous_index, &prev_entry) != SAVE_FS_LIST_ENTRY_SIZE)
return false;
return save_fs_list_free(ctx, index);
}
bool save_fs_list_change_key(save_filesystem_list_ctx_t *ctx, save_entry_key_t *old_key, save_entry_key_t *new_key) {
uint32_t index = save_fs_list_get_index_from_key(ctx, old_key, NULL);
uint32_t new_index = save_fs_list_get_index_from_key(ctx, new_key, NULL);
if (index == 0xFFFFFFFF) {
EPRINTF("Old key was not found!");
return false;
}
if (new_index != 0xFFFFFFFF) {
EPRINTF("New key already exists!");
return false;
}
save_fs_list_entry_t entry;
if (save_fs_list_read_entry(ctx, index, &entry) != SAVE_FS_LIST_ENTRY_SIZE)
return false;
entry.parent = new_key->parent;
memcpy(entry.name, new_key->name, SAVE_FS_LIST_MAX_NAME_LENGTH);
return save_fs_list_write_entry(ctx, index, &entry) == SAVE_FS_LIST_ENTRY_SIZE;
}
uint32_t save_fs_list_allocate_entry(save_filesystem_list_ctx_t *ctx) {
save_fs_list_entry_t free_list_head, used_list_head;
if (save_fs_list_read_entry(ctx, ctx->free_list_head_index, &free_list_head) != SAVE_FS_LIST_ENTRY_SIZE)
return 0;
if (save_fs_list_read_entry(ctx, ctx->used_list_head_index, &used_list_head) != SAVE_FS_LIST_ENTRY_SIZE)
return 0;
uint32_t allocated_index = free_list_head.next;
if (allocated_index != 0) {
save_fs_list_entry_t first_free_entry;
if (save_fs_list_read_entry(ctx, allocated_index, &first_free_entry) != SAVE_FS_LIST_ENTRY_SIZE)
return 0;
free_list_head.next = first_free_entry.next;
first_free_entry.next = used_list_head.next;
used_list_head.next = allocated_index;
if (save_fs_list_write_entry(ctx, ctx->free_list_head_index, &free_list_head) != SAVE_FS_LIST_ENTRY_SIZE)
return 0;
if (save_fs_list_write_entry(ctx, ctx->used_list_head_index, &used_list_head) != SAVE_FS_LIST_ENTRY_SIZE)
return 0;
if (save_fs_list_write_entry(ctx, allocated_index, &first_free_entry) != SAVE_FS_LIST_ENTRY_SIZE)
return 0;
return allocated_index;
}
uint32_t length = save_fs_list_get_length(ctx);
uint32_t capacity = save_fs_list_get_capacity(ctx);
if (capacity == 0 || length >= capacity) {
uint64_t current_size, new_size;
save_allocation_table_storage_get_size(&ctx->storage, &current_size);
if (!save_allocation_table_storage_set_size(&ctx->storage, current_size + 0x4000))
return 0;
save_allocation_table_storage_get_size(&ctx->storage, &new_size);
if (!save_fs_list_set_capacity(ctx, (uint32_t)(new_size / sizeof(save_fs_list_entry_t))))
return 0;
}
if (!save_fs_list_set_length(ctx, length + 1))
return 0;
save_fs_list_entry_t new_entry;
if (save_fs_list_read_entry(ctx, length, &new_entry) != SAVE_FS_LIST_ENTRY_SIZE)
return 0;
new_entry.next = used_list_head.next;
used_list_head.next = length;
if (save_fs_list_write_entry(ctx, ctx->used_list_head_index, &used_list_head) != SAVE_FS_LIST_ENTRY_SIZE)
return 0;
if (save_fs_list_write_entry(ctx, length, &new_entry) != SAVE_FS_LIST_ENTRY_SIZE)
return 0;
return length;
}
uint32_t save_fs_list_add(save_filesystem_list_ctx_t *ctx, const save_entry_key_t *key, const save_table_entry_t *value) {
uint32_t index = save_fs_list_get_index_from_key(ctx, key, NULL);
if (index != 0xFFFFFFFF) {
save_fs_list_set_value(ctx, index, value);
return index;
}
index = save_fs_list_allocate_entry(ctx);
if (index == 0) {
EPRINTF("Failed to allocate FS list entry!");
return 0;
}
save_fs_list_entry_t entry;
save_fs_list_read_entry(ctx, index, &entry);
memcpy(&entry.value, value, sizeof(save_table_entry_t));
entry.parent = key->parent;
memcpy(entry.name, key->name, SAVE_FS_LIST_MAX_NAME_LENGTH);
save_fs_list_write_entry(ctx, index, &entry);
return index;
}

View File

@ -0,0 +1,99 @@
/*
* Copyright (c) 2019-2020 shchmue
*
* 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/>.
*/
/*
ISC License
hactool Copyright (c) 2018, SciresM
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef _SAVE_FS_LIST_H_
#define _SAVE_FS_LIST_H_
#include "allocation_table_storage.h"
#include "save_fs_entry.h"
#include <assert.h>
#include <stdint.h>
#define SAVE_FS_LIST_ENTRY_SIZE 0x60
#define SAVE_FS_LIST_MAX_NAME_LENGTH 0x40
typedef struct {
uint32_t free_list_head_index;
uint32_t used_list_head_index;
allocation_table_storage_ctx_t storage;
} save_filesystem_list_ctx_t;
typedef struct {
uint32_t next_sibling;
union { /* Save table entry type. Size = 0x14. */
save_file_info_t save_file_info;
save_find_position_t save_find_position;
};
} save_table_entry_t;
static_assert(sizeof(save_table_entry_t) == 0x18, "Save table entry size is wrong!");
typedef struct {
uint32_t parent;
char name[SAVE_FS_LIST_MAX_NAME_LENGTH];
save_table_entry_t value;
uint32_t next;
} save_fs_list_entry_t;
typedef struct {
uint32_t list_size;
uint32_t list_capacity;
uint8_t rsvd[0x54];
uint32_t next;
} save_fs_list_entry_meta_t;
static_assert(sizeof(save_fs_list_entry_t) == 0x60, "Save filesystem list entry size is wrong!");
static ALWAYS_INLINE uint32_t save_fs_list_read_entry(save_filesystem_list_ctx_t *ctx, uint32_t index, void *entry) {
return save_allocation_table_storage_read(&ctx->storage, entry, index * SAVE_FS_LIST_ENTRY_SIZE, SAVE_FS_LIST_ENTRY_SIZE);
}
static ALWAYS_INLINE uint32_t save_fs_list_write_entry(save_filesystem_list_ctx_t *ctx, uint32_t index, const void *entry) {
return save_allocation_table_storage_write(&ctx->storage, entry, index * SAVE_FS_LIST_ENTRY_SIZE, SAVE_FS_LIST_ENTRY_SIZE);
}
void save_fs_list_init(save_filesystem_list_ctx_t *ctx);
uint32_t save_fs_list_get_index_from_key(save_filesystem_list_ctx_t *ctx, const save_entry_key_t *key, uint32_t *prev_index);
bool save_fs_list_get_value_by_index(save_filesystem_list_ctx_t *ctx, uint32_t index, save_table_entry_t *value);
bool save_fs_list_get_value_and_name(save_filesystem_list_ctx_t *ctx, uint32_t index, save_table_entry_t *value, char *name);
bool save_fs_list_try_get_value_by_index(save_filesystem_list_ctx_t *ctx, uint32_t index, save_table_entry_t *value);
bool save_fs_list_try_get_value_by_key(save_filesystem_list_ctx_t *ctx, save_entry_key_t *key, save_table_entry_t *value);
bool save_fs_list_try_get_value_and_name(save_filesystem_list_ctx_t *ctx, uint32_t index, save_table_entry_t *value, char *name);
bool save_fs_list_set_value(save_filesystem_list_ctx_t *ctx, uint32_t index, const save_table_entry_t *value);
bool save_fs_list_remove(save_filesystem_list_ctx_t *ctx, const save_entry_key_t *key);
bool save_fs_list_change_key(save_filesystem_list_ctx_t *ctx, save_entry_key_t *old_key, save_entry_key_t *new_key);
uint32_t save_fs_list_add(save_filesystem_list_ctx_t *ctx, const save_entry_key_t *key, const save_table_entry_t *value);
#endif

View File

@ -0,0 +1,241 @@
/*
* Copyright (c) 2019-2020 shchmue
*
* 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/>.
*/
/*
ISC License
hactool Copyright (c) 2018, SciresM
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "storage.h"
#include "cached_storage.h"
#include "hierarchical_duplex_storage.h"
#include "hierarchical_integrity_verification_storage.h"
#include "journal_storage.h"
#include "remap_storage.h"
#include <gfx_utils.h>
#include <libs/fatfs/ff.h>
#include <string.h>
void storage_init(storage *this, const storage_vt *vt, void *ctx) {
this->vt = vt;
this->ctx = ctx;
}
void substorage_init(substorage *this, const storage_vt *vt, void *ctx, uint64_t offset, uint64_t length) {
storage_init(&this->base_storage, vt, ctx);
this->offset = offset;
this->length = length;
}
bool substorage_init_from_other(substorage *this, const substorage *other, uint64_t offset, uint64_t length) {
if (offset + length > other->length) {
EPRINTF("Invalid size for substorage init!");
EPRINTFARGS("ofs %x len %x size %x", (uint32_t)offset, (uint32_t)length, (uint32_t)other->length);
return false;
}
substorage_init(this, other->base_storage.vt, other->base_storage.ctx, other->offset + offset, length);
return true;
}
void sector_storage_init(sector_storage *ctx, substorage *base_storage, uint32_t sector_size) {
memcpy(&ctx->base_storage, base_storage, sizeof(substorage));
ctx->sector_size = sector_size;
ctx->length = base_storage->length;
ctx->sector_count = (uint32_t)(DIV_ROUND_UP(ctx->length, ctx->sector_size));
}
uint32_t sector_storage_read(sector_storage *ctx, void *buffer, uint64_t offset, uint64_t count) {
uint64_t remaining = count;
uint64_t in_offset = offset;
uint32_t out_offset = 0;
uint32_t sector_size = ctx->sector_size;
while (remaining) {
uint32_t sector_pos = (uint32_t)(in_offset % sector_size);
uint32_t bytes_to_read = MIN((uint32_t)remaining, (uint32_t)(sector_size - sector_pos));
substorage_read(&ctx->base_storage, (uint8_t *)buffer + out_offset, in_offset, bytes_to_read);
out_offset += bytes_to_read;
in_offset += bytes_to_read;
remaining -= bytes_to_read;
}
return out_offset;
}
uint32_t sector_storage_write(sector_storage *ctx, const void *buffer, uint64_t offset, uint64_t count) {
uint64_t remaining = count;
uint64_t in_offset = offset;
uint32_t out_offset = 0;
uint32_t sector_size = ctx->sector_size;
while (remaining) {
uint32_t sector_pos = (uint32_t)(in_offset % sector_size);
uint32_t bytes_to_write = MIN((uint32_t)remaining, (uint32_t)(sector_size - sector_pos));
substorage_write(&ctx->base_storage, (uint8_t *)buffer + out_offset, in_offset, bytes_to_write);
out_offset += bytes_to_write;
in_offset += bytes_to_write;
remaining -= bytes_to_write;
}
return out_offset;
}
uint32_t save_hierarchical_integrity_verification_storage_read_wrapper(void *ctx, void *buffer, uint64_t offset, uint64_t count) {
hierarchical_integrity_verification_storage_ctx_t *storage = (hierarchical_integrity_verification_storage_ctx_t *)ctx;
return save_cached_storage_read(storage->data_level, buffer, offset, count);
}
uint32_t save_hierarchical_integrity_verification_storage_write_wrapper(void *ctx, const void *buffer, uint64_t offset, uint64_t count) {
hierarchical_integrity_verification_storage_ctx_t *storage = (hierarchical_integrity_verification_storage_ctx_t *)ctx;
return save_cached_storage_write(storage->data_level, buffer, offset, count);
}
void save_hierarchical_integrity_verification_storage_get_size_wrapper(void *ctx, uint64_t *out_size) {
hierarchical_integrity_verification_storage_ctx_t *storage = (hierarchical_integrity_verification_storage_ctx_t *)ctx;
*out_size = storage->length;
}
uint32_t memory_storage_read(uint8_t *storage, void *buffer, uint64_t offset, uint64_t count) {
memcpy(buffer, storage + offset, count);
return count;
}
uint32_t memory_storage_write(uint8_t *storage, const void *buffer, uint64_t offset, uint64_t count) {
memcpy(storage + offset, buffer, count);
return count;
}
uint32_t memory_storage_read_wrapper(void *ctx, void *buffer, uint64_t offset, uint64_t count) {
return memory_storage_read((uint8_t *)ctx, buffer, offset, count);
}
uint32_t memory_storage_write_wrapper(void *ctx, const void *buffer, uint64_t offset, uint64_t count) {
return memory_storage_write((uint8_t *)ctx, buffer, offset, count);
}
uint32_t save_file_read(FIL *fp, void *buffer, uint64_t offset, uint64_t count) {
UINT bytes_read = 0;
if (f_lseek(fp, offset) || f_read(fp, buffer, count, &bytes_read) || bytes_read != count) {
EPRINTFARGS("Failed to read file at offset %x!\nRead %x bytes. Req %x bytes.", (uint32_t)offset, bytes_read, (uint32_t)count);
return 0;
}
return bytes_read;
}
uint32_t save_file_write(FIL *fp, const void *buffer, uint64_t offset, uint64_t count) {
UINT bytes_written = 0;
if (f_lseek(fp, offset) || f_write(fp, buffer, count, &bytes_written) || bytes_written != count) {
EPRINTFARGS("Failed to write file at offset %x!", (uint32_t)offset);
return 0;
}
return bytes_written;
}
void save_file_get_size(FIL *fp, uint64_t *out_size) {
*out_size = f_size(fp);
}
uint32_t save_file_read_wrapper(void *ctx, void *buffer, uint64_t offset, uint64_t count) {
return save_file_read((FIL *)ctx, buffer, offset, count);
}
uint32_t save_file_write_wrapper(void *ctx, const void *buffer, uint64_t offset, uint64_t count) {
return save_file_write((FIL *)ctx, buffer, offset, count);
}
void save_file_get_size_wrapper(void *ctx, uint64_t *out_size) {
save_file_get_size((FIL *)ctx, out_size);
}
uint32_t save_remap_storage_read_wrapper(void *ctx, void *buffer, uint64_t offset, uint64_t count) {
return save_remap_storage_read((remap_storage_ctx_t *)ctx, buffer, offset, count);
}
uint32_t save_remap_storage_write_wrapper(void *ctx, const void *buffer, uint64_t offset, uint64_t count) {
return save_remap_storage_write((remap_storage_ctx_t *)ctx, buffer, offset, count);
}
void save_remap_storage_get_size_wrapper(__attribute__((unused)) void *ctx, uint64_t *out_size) {
*out_size = -1;
}
uint32_t save_journal_storage_read_wrapper(void *ctx, void *buffer, uint64_t offset, uint64_t count) {
return save_journal_storage_read((journal_storage_ctx_t *)ctx, buffer, offset, count);
}
uint32_t save_journal_storage_write_wrapper(void *ctx, const void *buffer, uint64_t offset, uint64_t count) {
return save_journal_storage_write((journal_storage_ctx_t *)ctx, buffer, offset, count);
}
void save_journal_storage_get_size_wrapper(void *ctx, uint64_t *out_size) {
journal_storage_ctx_t *journal = (journal_storage_ctx_t *)ctx;
*out_size = journal->length;
}
uint32_t save_ivfc_storage_read_wrapper(void *ctx, void *buffer, uint64_t offset, uint64_t count) {
return save_ivfc_storage_read((integrity_verification_storage_ctx_t *)ctx, buffer, offset, count) ? count : 0;
}
uint32_t save_ivfc_storage_write_wrapper(void *ctx, const void *buffer, uint64_t offset, uint64_t count) {
return save_ivfc_storage_write((integrity_verification_storage_ctx_t *)ctx, buffer, offset, count) ? count : 0;
}
void save_ivfc_storage_get_size_wrapper(void *ctx, uint64_t *out_size) {
integrity_verification_storage_ctx_t *ivfc = (integrity_verification_storage_ctx_t *)ctx;
*out_size = ivfc->base_storage.length;
}
uint32_t save_hierarchical_duplex_storage_read(hierarchical_duplex_storage_ctx_t *ctx, void *buffer, uint64_t offset, uint64_t count) {
return save_duplex_storage_read(ctx->data_layer, buffer, offset, count);
}
uint32_t save_hierarchical_duplex_storage_write(hierarchical_duplex_storage_ctx_t *ctx, const void *buffer, uint64_t offset, uint64_t count) {
return save_duplex_storage_write(ctx->data_layer, buffer, offset, count);
}
uint32_t save_hierarchical_duplex_storage_read_wrapper(void *ctx, void *buffer, uint64_t offset, uint64_t count) {
return save_hierarchical_duplex_storage_read((hierarchical_duplex_storage_ctx_t *)ctx, buffer, offset, count);
}
uint32_t save_hierarchical_duplex_storage_write_wrapper(void *ctx, const void *buffer, uint64_t offset, uint64_t count) {
return save_hierarchical_duplex_storage_write((hierarchical_duplex_storage_ctx_t *)ctx, buffer, offset, count);
}
void save_hierarchical_duplex_storage_get_size_wrapper(void *ctx, uint64_t *out_size) {
hierarchical_duplex_storage_ctx_t *duplex = (hierarchical_duplex_storage_ctx_t *)ctx;
*out_size = duplex->_length;
}

View File

@ -0,0 +1,169 @@
/*
* Copyright (c) 2019-2020 shchmue
*
* 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/>.
*/
/*
ISC License
hactool Copyright (c) 2018, SciresM
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef HACTOOL_STORAGE_H
#define HACTOOL_STORAGE_H
#include <stdint.h>
#include <utils/types.h>
typedef struct {
uint32_t (*read)(void *ctx, void *buffer, uint64_t offset, uint64_t count);
uint32_t (*write)(void *ctx, const void *buffer, uint64_t offset, uint64_t count);
void (*set_size)(void *ctx, uint64_t size);
void (*get_size)(void *ctx, uint64_t *out_size);
} storage_vt;
typedef struct {
const storage_vt *vt;
void *ctx;
} storage;
void storage_init(storage *this, const storage_vt *vt, void *ctx);
typedef struct {
uint64_t offset;
uint64_t length;
storage base_storage;
} substorage;
void substorage_init(substorage *this, const storage_vt *vt, void *ctx, uint64_t offset, uint64_t length);
bool substorage_init_from_other(substorage *this, const substorage *other, uint64_t offset, uint64_t length);
static ALWAYS_INLINE uint32_t substorage_read(substorage *ctx, void *buffer, uint64_t offset, uint64_t count) {
return ctx->base_storage.vt->read(ctx->base_storage.ctx, buffer, ctx->offset + offset, count);
}
static ALWAYS_INLINE uint32_t substorage_write(substorage *ctx, const void *buffer, uint64_t offset, uint64_t count) {
return ctx->base_storage.vt->write(ctx->base_storage.ctx, buffer, ctx->offset + offset, count);
}
static ALWAYS_INLINE void substorage_get_size(substorage *ctx, uint64_t *out_size) {
ctx->base_storage.vt->get_size(ctx->base_storage.ctx, out_size);
}
typedef struct {
substorage base_storage;
uint32_t sector_size;
uint32_t sector_count;
uint64_t length;
} sector_storage;
void sector_storage_init(sector_storage *ctx, substorage *base_storage, uint32_t sector_size);
uint32_t save_hierarchical_integrity_verification_storage_read_wrapper(void *ctx, void *buffer, uint64_t offset, uint64_t count);
uint32_t save_hierarchical_integrity_verification_storage_write_wrapper(void *ctx, const void *buffer, uint64_t offset, uint64_t count);
void save_hierarchical_integrity_verification_storage_get_size_wrapper(void *ctx, uint64_t *out_size);
static const storage_vt hierarchical_integrity_verification_storage_vt = {
save_hierarchical_integrity_verification_storage_read_wrapper,
save_hierarchical_integrity_verification_storage_write_wrapper,
NULL,
save_hierarchical_integrity_verification_storage_get_size_wrapper
};
uint32_t memory_storage_read_wrapper(void *ctx, void *buffer, uint64_t offset, uint64_t count);
uint32_t memory_storage_write_wrapper(void *ctx, const void *buffer, uint64_t offset, uint64_t count);
static const storage_vt memory_storage_vt = {
memory_storage_read_wrapper,
memory_storage_write_wrapper,
NULL,
NULL
};
uint32_t save_file_read_wrapper(void *ctx, void *buffer, uint64_t offset, uint64_t count);
uint32_t save_file_write_wrapper(void *ctx, const void *buffer, uint64_t offset, uint64_t count);
void save_file_get_size_wrapper(void *ctx, uint64_t *out_size);
static const storage_vt file_storage_vt = {
save_file_read_wrapper,
save_file_write_wrapper,
NULL,
save_file_get_size_wrapper
};
uint32_t save_remap_storage_read_wrapper(void *ctx, void *buffer, uint64_t offset, uint64_t count);
uint32_t save_remap_storage_write_wrapper(void *ctx, const void *buffer, uint64_t offset, uint64_t count);
void save_remap_storage_get_size_wrapper(void *ctx, uint64_t *out_size);
static const storage_vt remap_storage_vt = {
save_remap_storage_read_wrapper,
save_remap_storage_write_wrapper,
NULL,
save_remap_storage_get_size_wrapper
};
uint32_t save_journal_storage_read_wrapper(void *ctx, void *buffer, uint64_t offset, uint64_t count);
uint32_t save_journal_storage_write_wrapper(void *ctx, const void *buffer, uint64_t offset, uint64_t count);
void save_journal_storage_get_size_wrapper(void *ctx, uint64_t *out_size);
static const storage_vt journal_storage_vt = {
save_journal_storage_read_wrapper,
save_journal_storage_write_wrapper,
NULL,
save_journal_storage_get_size_wrapper
};
uint32_t save_ivfc_storage_read_wrapper(void *ctx, void *buffer, uint64_t offset, uint64_t count);
uint32_t save_ivfc_storage_write_wrapper(void *ctx, const void *buffer, uint64_t offset, uint64_t count);
void save_ivfc_storage_get_size_wrapper(void *ctx, uint64_t *out_size);
static const storage_vt ivfc_storage_vt = {
save_ivfc_storage_read_wrapper,
save_ivfc_storage_write_wrapper,
NULL,
save_ivfc_storage_get_size_wrapper
};
uint32_t save_hierarchical_duplex_storage_read_wrapper(void *ctx, void *buffer, uint64_t offset, uint64_t count);
uint32_t save_hierarchical_duplex_storage_write_wrapper(void *ctx, const void *buffer, uint64_t offset, uint64_t count);
void save_hierarchical_duplex_storage_get_size_wrapper(void *ctx, uint64_t *out_size);
static const storage_vt hierarchical_duplex_storage_vt = {
save_hierarchical_duplex_storage_read_wrapper,
save_hierarchical_duplex_storage_write_wrapper,
NULL,
save_hierarchical_duplex_storage_get_size_wrapper
};
static ALWAYS_INLINE bool is_range_valid(uint64_t offset, uint64_t size, uint64_t total_size) {
return offset >= 0 &&
size >= 0 &&
size <= total_size &&
offset <= total_size - size;
}
#endif

View File

@ -1,829 +0,0 @@
#include <stdint.h>
#include <string.h>
#include "save.h"
#include <gfx_utils.h>
#include <mem/heap.h>
#include <sec/se.h>
#include <utils/types.h>
#include <utils/util.h>
#define REMAP_ENTRY_LENGTH 0x20
static inline void save_bitmap_set_bit(void *buffer, size_t bit_offset) {
*((uint8_t *)buffer + (bit_offset >> 3)) |= 1 << (bit_offset & 7);
}
static inline void save_bitmap_clear_bit(void *buffer, size_t bit_offset) {
*((uint8_t *)buffer + (bit_offset >> 3)) &= ~(uint8_t)(1 << (bit_offset & 7));
}
static inline uint8_t save_bitmap_check_bit(const void *buffer, size_t bit_offset) {
return *((uint8_t *)buffer + (bit_offset >> 3)) & (1 << (bit_offset & 7));
}
void save_duplex_storage_init(duplex_storage_ctx_t *ctx, duplex_fs_layer_info_t *layer, void *bitmap, uint64_t bitmap_size) {
ctx->data_a = layer->data_a;
ctx->data_b = layer->data_b;
ctx->bitmap_storage = (uint8_t *)bitmap;
ctx->block_size = 1 << layer->info.block_size_power;
ctx->bitmap.data = ctx->bitmap_storage;
ctx->bitmap.bitmap = malloc(bitmap_size >> 3);
uint32_t bits_remaining = bitmap_size;
uint32_t bitmap_pos = 0;
uint32_t *buffer_pos = (uint32_t *)bitmap;
while (bits_remaining) {
uint32_t bits_to_read = bits_remaining < 32 ? bits_remaining : 32;
uint32_t val = *buffer_pos;
for (uint32_t i = 0; i < bits_to_read; i++) {
if (val & 0x80000000)
save_bitmap_set_bit(ctx->bitmap.bitmap, bitmap_pos);
else
save_bitmap_clear_bit(ctx->bitmap.bitmap, bitmap_pos);
bitmap_pos++;
bits_remaining--;
val <<= 1;
}
buffer_pos++;
}
}
uint32_t save_duplex_storage_read(duplex_storage_ctx_t *ctx, void *buffer, uint64_t offset, size_t count) {
uint64_t in_pos = offset;
uint32_t out_pos = 0;
uint32_t remaining = count;
while (remaining) {
uint32_t block_num = (uint32_t)(in_pos / ctx->block_size);
uint32_t block_pos = (uint32_t)(in_pos % ctx->block_size);
uint32_t bytes_to_read = ctx->block_size - block_pos < remaining ? ctx->block_size - block_pos : remaining;
uint8_t *data = save_bitmap_check_bit(ctx->bitmap.bitmap, block_num) ? ctx->data_b : ctx->data_a;
memcpy((uint8_t *)buffer + out_pos, data + in_pos, bytes_to_read);
out_pos += bytes_to_read;
in_pos += bytes_to_read;
remaining -= bytes_to_read;
}
return out_pos;
}
remap_segment_ctx_t *save_remap_init_segments(remap_header_t *header, remap_entry_ctx_t *map_entries, uint32_t num_map_entries) {
remap_segment_ctx_t *segments = calloc(1, sizeof(remap_segment_ctx_t) * header->map_segment_count);
unsigned int entry_idx = 0;
for (unsigned int i = 0; i < header->map_segment_count; i++) {
remap_segment_ctx_t *seg = &segments[i];
seg->entry_count = 0;
seg->entries = malloc(sizeof(remap_entry_ctx_t *));
seg->entries[seg->entry_count++] = &map_entries[entry_idx];
seg->offset = map_entries[entry_idx].virtual_offset;
map_entries[entry_idx++].segment = seg;
while (entry_idx < num_map_entries && map_entries[entry_idx - 1].virtual_offset_end == map_entries[entry_idx].virtual_offset) {
map_entries[entry_idx].segment = seg;
map_entries[entry_idx - 1].next = &map_entries[entry_idx];
remap_entry_ctx_t **ptr = calloc(1, sizeof(remap_entry_ctx_t *) * (seg->entry_count + 1));
memcpy(ptr, seg->entries, sizeof(remap_entry_ctx_t *) * (seg->entry_count));
free(seg->entries);
seg->entries = ptr;
seg->entries[seg->entry_count++] = &map_entries[entry_idx++];
}
seg->length = seg->entries[seg->entry_count - 1]->virtual_offset_end - seg->entries[0]->virtual_offset;
}
return segments;
}
remap_entry_ctx_t *save_remap_get_map_entry(remap_storage_ctx_t *ctx, uint64_t offset) {
uint32_t segment_idx = (uint32_t)(offset >> (64 - ctx->header->segment_bits));
if (segment_idx < ctx->header->map_segment_count) {
for (unsigned int i = 0; i < ctx->segments[segment_idx].entry_count; i++)
if (ctx->segments[segment_idx].entries[i]->virtual_offset_end > offset)
return ctx->segments[segment_idx].entries[i];
}
return NULL;
}
uint32_t save_remap_read(remap_storage_ctx_t *ctx, void *buffer, uint64_t offset, size_t count) {
remap_entry_ctx_t *entry = save_remap_get_map_entry(ctx, offset);
uint64_t in_pos = offset;
uint32_t out_pos = 0;
uint32_t remaining = count;
while (remaining) {
uint64_t entry_pos = in_pos - entry->virtual_offset;
uint32_t bytes_to_read = entry->virtual_offset_end - in_pos < remaining ? (uint32_t)(entry->virtual_offset_end - in_pos) : remaining;
switch (ctx->type) {
case STORAGE_BYTES:
f_lseek(ctx->file, ctx->base_storage_offset + entry->physical_offset + entry_pos);
f_read(ctx->file, (uint8_t *)buffer + out_pos, bytes_to_read, NULL);
break;
case STORAGE_DUPLEX:
save_duplex_storage_read(ctx->duplex, (uint8_t *)buffer + out_pos, ctx->base_storage_offset + entry->physical_offset + entry_pos, bytes_to_read);
break;
default:
break;
}
out_pos += bytes_to_read;
in_pos += bytes_to_read;
remaining -= bytes_to_read;
if (in_pos >= entry->virtual_offset_end)
entry = entry->next;
}
return out_pos;
}
uint32_t save_journal_storage_read(journal_storage_ctx_t *ctx, remap_storage_ctx_t *remap, void *buffer, uint64_t offset, size_t count) {
uint64_t in_pos = offset;
uint32_t out_pos = 0;
uint32_t remaining = count;
while (remaining) {
uint32_t block_num = (uint32_t)(in_pos / ctx->block_size);
uint32_t block_pos = (uint32_t)(in_pos % ctx->block_size);
uint64_t physical_offset = ctx->map.entries[block_num].physical_index * ctx->block_size + block_pos;
uint32_t bytes_to_read = ctx->block_size - block_pos < remaining ? ctx->block_size - block_pos : remaining;
save_remap_read(remap, (uint8_t *)buffer + out_pos, ctx->journal_data_offset + physical_offset, bytes_to_read);
out_pos += bytes_to_read;
in_pos += bytes_to_read;
remaining -= bytes_to_read;
}
return out_pos;
}
void save_ivfc_storage_init(hierarchical_integrity_verification_storage_ctx_t *ctx, uint64_t master_hash_offset, ivfc_save_hdr_t *ivfc) {
ivfc_level_save_ctx_t *levels = ctx->levels;
levels[0].type = STORAGE_BYTES;
levels[0].hash_offset = master_hash_offset;
for (unsigned int i = 1; i < 4; i++) {
ivfc_level_hdr_t *level = &ivfc->level_headers[i - 1];
levels[i].type = STORAGE_REMAP;
levels[i].data_offset = level->logical_offset;
levels[i].data_size = level->hash_data_size;
}
if (ivfc->num_levels == 5) {
ivfc_level_hdr_t *data_level = &ivfc->level_headers[ivfc->num_levels - 2];
levels[ivfc->num_levels - 1].type = STORAGE_JOURNAL;
levels[ivfc->num_levels - 1].data_offset = data_level->logical_offset;
levels[ivfc->num_levels - 1].data_size = data_level->hash_data_size;
}
struct salt_source_t {
char string[50];
uint32_t length;
};
static const struct salt_source_t salt_sources[6] = {
{"HierarchicalIntegrityVerificationStorage::Master", 48},
{"HierarchicalIntegrityVerificationStorage::L1", 44},
{"HierarchicalIntegrityVerificationStorage::L2", 44},
{"HierarchicalIntegrityVerificationStorage::L3", 44},
{"HierarchicalIntegrityVerificationStorage::L4", 44},
{"HierarchicalIntegrityVerificationStorage::L5", 44}
};
integrity_verification_info_ctx_t init_info[ivfc->num_levels];
init_info[0].data = &levels[0];
init_info[0].block_size = 0;
for (unsigned int i = 1; i < ivfc->num_levels; i++) {
init_info[i].data = &levels[i];
init_info[i].block_size = 1 << ivfc->level_headers[i - 1].block_size;
se_calc_hmac_sha256(init_info[i].salt, ivfc->salt_source, 0x20, salt_sources[i - 1].string, salt_sources[i - 1].length);
}
ctx->integrity_storages[0].next_level = NULL;
ctx->level_validities = malloc(sizeof(validity_t *) * (ivfc->num_levels - 1));
for (unsigned int i = 1; i < ivfc->num_levels; i++) {
integrity_verification_storage_ctx_t *level_data = &ctx->integrity_storages[i - 1];
level_data->hash_storage = &levels[i - 1];
level_data->base_storage = &levels[i];
level_data->sector_size = init_info[i].block_size;
level_data->_length = init_info[i].data->data_size;
level_data->sector_count = (level_data->_length + level_data->sector_size - 1) / level_data->sector_size;
memcpy(level_data->salt, init_info[i].salt, 0x20);
level_data->block_validities = calloc(1, sizeof(validity_t) * level_data->sector_count);
ctx->level_validities[i - 1] = level_data->block_validities;
if (i > 1) {
level_data->next_level = &ctx->integrity_storages[i - 2];
}
}
ctx->data_level = &levels[ivfc->num_levels - 1];
ctx->_length = ctx->integrity_storages[ivfc->num_levels - 2]._length;
}
size_t save_ivfc_level_fread(ivfc_level_save_ctx_t *ctx, void *buffer, uint64_t offset, size_t count) {
switch (ctx->type) {
case STORAGE_BYTES:
f_lseek(ctx->save_ctx->file, ctx->hash_offset + offset);
UINT br = 0;
f_read(ctx->save_ctx->file, buffer, count, &br);
return br;
case STORAGE_REMAP:
save_remap_read(&ctx->save_ctx->meta_remap_storage, buffer, ctx->data_offset + offset, count);
return count;
case STORAGE_JOURNAL:
save_journal_storage_read(&ctx->save_ctx->journal_storage, &ctx->save_ctx->data_remap_storage, buffer, ctx->data_offset + offset, count);
return count;
default:
return 0;
}
}
void save_ivfc_storage_read(integrity_verification_storage_ctx_t *ctx, void *buffer, uint64_t offset, size_t count, uint32_t verify) {
if (count > ctx->sector_size) {
EPRINTF("IVFC read exceeds sector size!");
return;
}
uint64_t block_index = offset / ctx->sector_size;
if (ctx->block_validities[block_index] == VALIDITY_INVALID && verify) {
EPRINTFARGS("Hash error from previous check\n found at offset %x count %x!", (u32)offset, count);
return;
}
uint8_t hash_buffer[0x20] = {0};
uint8_t zeroes[0x20] = {0};
uint64_t hash_pos = block_index * 0x20;
if (ctx->next_level) {
save_ivfc_storage_read(ctx->next_level, hash_buffer, hash_pos, 0x20, verify);
} else {
save_ivfc_level_fread(ctx->hash_storage, hash_buffer, hash_pos, 0x20);
}
if (!memcmp(hash_buffer, zeroes, 0x20)) {
memset(buffer, 0, count);
ctx->block_validities[block_index] = VALIDITY_VALID;
return;
}
save_ivfc_level_fread(ctx->base_storage, buffer, offset, count);
if (!(verify && ctx->block_validities[block_index] == VALIDITY_UNCHECKED)) {
return;
}
uint8_t hash[0x20] = {0};
uint8_t *data_buffer = calloc(1, ctx->sector_size + 0x20);
memcpy(data_buffer, ctx->salt, 0x20);
memcpy(data_buffer + 0x20, buffer, ctx->sector_size);
se_calc_sha256(hash, data_buffer, ctx->sector_size + 0x20);
hash[0x1F] |= 0x80;
free(data_buffer);
if (memcmp(hash_buffer, hash, 0x20) != 0) {
ctx->block_validities[block_index] = VALIDITY_INVALID;
} else {
ctx->block_validities[block_index] = VALIDITY_VALID;
}
if (ctx->block_validities[block_index] == VALIDITY_INVALID && verify) {
EPRINTFARGS("Hash error from current check\n found at offset %x count %x!", (u32)offset, count);
return;
}
}
uint32_t save_allocation_table_read_entry_with_length(allocation_table_ctx_t *ctx, allocation_table_entry_t *entry) {
uint32_t length = 1;
uint32_t entry_index = allocation_table_block_to_entry_index(entry->next);
uint32_t offset = entry_index * SAVE_FAT_ENTRY_SIZE;
allocation_table_entry_t *entries = (allocation_table_entry_t *)((uint8_t *)(ctx->base_storage) + offset);
if (allocation_table_is_single_block_segment(&entries[0])) {
if (allocation_table_is_range_entry(&entries[0])) {
EPRINTF("Invalid range entry in allocation table!");
return 0;
}
} else {
length = entries[1].next - entry_index + 1;
}
if (allocation_table_is_list_end(&entries[0])) {
entry->next = 0xFFFFFFFF;
} else {
entry->next = allocation_table_entry_index_to_block(allocation_table_get_next(&entries[0]));
}
if (allocation_table_is_list_start(&entries[0])) {
entry->prev = 0xFFFFFFFF;
} else {
entry->prev = allocation_table_entry_index_to_block(allocation_table_get_prev(&entries[0]));
}
return length;
}
uint32_t save_allocation_table_get_list_length(allocation_table_ctx_t *ctx, uint32_t block_index) {
allocation_table_entry_t entry;
entry.next = block_index;
uint32_t total_length = 0;
uint32_t table_size = ctx->header->allocation_table_block_count;
uint32_t nodes_iterated = 0;
while (entry.next != 0xFFFFFFFF) {
total_length += save_allocation_table_read_entry_with_length(ctx, &entry);
nodes_iterated++;
if (nodes_iterated > table_size) {
EPRINTF("Cycle detected in allocation table!");
return 0;
}
}
return total_length;
}
uint64_t save_allocation_table_get_free_space_size(save_filesystem_ctx_t *ctx) {
uint32_t free_list_start = save_allocation_table_get_free_list_block_index(&ctx->allocation_table);
if (free_list_start == 0xFFFFFFFF) return 0;
return ctx->header->block_size * save_allocation_table_get_list_length(&ctx->allocation_table, free_list_start);
}
void save_allocation_table_iterator_begin(allocation_table_iterator_ctx_t *ctx, allocation_table_ctx_t *table, uint32_t initial_block) {
ctx->fat = table;
ctx->physical_block = initial_block;
ctx->virtual_block = 0;
allocation_table_entry_t entry = {0, 0};
entry.next = initial_block;
ctx->current_segment_size = save_allocation_table_read_entry_with_length(ctx->fat, &entry);
ctx->next_block = entry.next;
ctx->prev_block = entry.prev;
if (ctx->prev_block != 0xFFFFFFFF) {
EPRINTFARGS("Attempted to start FAT iteration from\n invalid block %x!", initial_block);
return;
}
}
int save_allocation_table_iterator_move_next(allocation_table_iterator_ctx_t *ctx) {
if (ctx->next_block == 0xFFFFFFFF) return 0;
ctx->virtual_block += ctx->current_segment_size;
ctx->physical_block = ctx->next_block;
allocation_table_entry_t entry = {0, 0};
entry.next = ctx->next_block;
ctx->current_segment_size = save_allocation_table_read_entry_with_length(ctx->fat, &entry);
ctx->next_block = entry.next;
ctx->prev_block = entry.prev;
return 1;
}
int save_allocation_table_iterator_move_prev(allocation_table_iterator_ctx_t *ctx) {
if (ctx->prev_block == 0xFFFFFFFF) return 0;
ctx->physical_block = ctx->prev_block;
allocation_table_entry_t entry = {0, 0};
entry.next = ctx->prev_block;
ctx->current_segment_size = save_allocation_table_read_entry_with_length(ctx->fat, &entry);
ctx->next_block = entry.next;
ctx->prev_block = entry.prev;
ctx->virtual_block -= ctx->current_segment_size;
return 1;
}
int save_allocation_table_iterator_seek(allocation_table_iterator_ctx_t *ctx, uint32_t block) {
while (1) {
if (block < ctx->virtual_block) {
if (!save_allocation_table_iterator_move_prev(ctx)) return 0;
} else if (block >= ctx->virtual_block + ctx->current_segment_size) {
if (!save_allocation_table_iterator_move_next(ctx)) return 0;
} else {
return 1;
}
}
}
uint32_t save_allocation_table_storage_read(allocation_table_storage_ctx_t *ctx, void *buffer, uint64_t offset, size_t count) {
allocation_table_iterator_ctx_t iterator;
save_allocation_table_iterator_begin(&iterator, ctx->fat, ctx->initial_block);
uint64_t in_pos = offset;
uint32_t out_pos = 0;
uint32_t remaining = count;
while (remaining) {
uint32_t block_num = (uint32_t)(in_pos / ctx->block_size);
save_allocation_table_iterator_seek(&iterator, block_num);
uint32_t segment_pos = (uint32_t)(in_pos - (uint64_t)iterator.virtual_block * ctx->block_size);
uint64_t physical_offset = iterator.physical_block * ctx->block_size + segment_pos;
uint32_t remaining_in_segment = iterator.current_segment_size * ctx->block_size - segment_pos;
uint32_t bytes_to_read = remaining < remaining_in_segment ? remaining : remaining_in_segment;
uint32_t sector_size = ctx->base_storage->integrity_storages[3].sector_size;
uint32_t chunk_remaining = bytes_to_read;
for (unsigned int i = 0; i < bytes_to_read; i += sector_size) {
uint32_t bytes_to_request = chunk_remaining < sector_size ? chunk_remaining : sector_size;
save_ivfc_storage_read(&ctx->base_storage->integrity_storages[3], (uint8_t *)buffer + out_pos + i, physical_offset + i, bytes_to_request, ctx->base_storage->data_level->save_ctx->tool_ctx.action & ACTION_VERIFY);
chunk_remaining -= bytes_to_request;
}
out_pos += bytes_to_read;
in_pos += bytes_to_read;
remaining -= bytes_to_read;
}
return out_pos;
}
uint32_t save_fs_list_get_capacity(save_filesystem_list_ctx_t *ctx) {
if (!ctx->capacity)
save_allocation_table_storage_read(&ctx->storage, &ctx->capacity, 4, 4);
return ctx->capacity;
}
uint32_t save_fs_list_read_entry(save_filesystem_list_ctx_t *ctx, uint32_t index, save_fs_list_entry_t *entry) {
return save_allocation_table_storage_read(&ctx->storage, entry, index * SAVE_FS_LIST_ENTRY_SIZE, SAVE_FS_LIST_ENTRY_SIZE);
}
int save_fs_list_get_value(save_filesystem_list_ctx_t *ctx, uint32_t index, save_fs_list_entry_t *value) {
if (index >= save_fs_list_get_capacity(ctx)) {
return 0;
}
save_fs_list_read_entry(ctx, index, value);
return 1;
}
uint32_t save_fs_list_get_index_from_key(save_filesystem_list_ctx_t *ctx, save_entry_key_t *key, uint32_t *prev_index) {
save_fs_list_entry_t entry;
uint32_t capacity = save_fs_list_get_capacity(ctx);
save_fs_list_read_entry(ctx, ctx->used_list_head_index, &entry);
uint32_t prev;
if (!prev_index) {
prev_index = &prev;
}
*prev_index = ctx->used_list_head_index;
uint32_t index = entry.next;
while (index) {
if (index > capacity) {
EPRINTFARGS("Save entry index %d out of range!", index);
*prev_index = 0xFFFFFFFF;
return 0xFFFFFFFF;
}
save_fs_list_read_entry(ctx, index, &entry);
if (entry.parent == key->parent && !strcmp(entry.name, key->name)) {
return index;
}
*prev_index = index;
index = entry.next;
}
*prev_index = 0xFFFFFFFF;
return 0xFFFFFFFF;
}
int save_hierarchical_file_table_find_path_recursive(hierarchical_save_file_table_ctx_t *ctx, save_entry_key_t *key, const char *path) {
key->parent = 0;
const char *pos = strchr(path, '/');
while (pos) {
memset(key->name, 0, SAVE_FS_LIST_MAX_NAME_LENGTH);
const char *tmp = strchr(pos, '/');
if (!tmp) {
memcpy(key->name, pos, strlen(pos));
break;
}
memcpy(key->name, pos, tmp - pos);
key->parent = save_fs_list_get_index_from_key(&ctx->directory_table, key, NULL);
if (key->parent == 0xFFFFFFFF)
return 0;
pos = tmp + 1;
}
return 1;
}
int save_hierarchical_file_table_find_next_file(hierarchical_save_file_table_ctx_t *ctx, save_find_position_t *position, save_file_info_t *info, char *name) {
if (position->next_file == 0) {
return 0;
}
save_fs_list_entry_t entry;
if(!save_fs_list_get_value(&ctx->file_table, position->next_file, &entry)) {
return 0;
}
position->next_file = entry.value.next_sibling;
memcpy(name, &entry.name, SAVE_FS_LIST_MAX_NAME_LENGTH);
memcpy(info, &entry.value.save_file_info, sizeof(save_file_info_t));
return 1;
}
int save_hierarchical_file_table_find_next_directory(hierarchical_save_file_table_ctx_t *ctx, save_find_position_t *position, char *name) {
if (position->next_directory == 0) {
return 0;
}
save_fs_list_entry_t entry;
if(!save_fs_list_get_value(&ctx->directory_table, position->next_directory, &entry)) {
return 0;
}
position->next_directory = entry.value.next_sibling;
memcpy(name, &entry.name, SAVE_FS_LIST_MAX_NAME_LENGTH);
return 1;
}
int save_hierarchical_file_table_get_file_entry_by_path(hierarchical_save_file_table_ctx_t *ctx, const char *path, save_fs_list_entry_t *entry) {
save_entry_key_t key;
if (!save_hierarchical_file_table_find_path_recursive(ctx, &key, path)) {
EPRINTF("Unable to locate file.");
return 0;
}
u32 index = save_fs_list_get_index_from_key(&ctx->file_table, &key, NULL);
if (index == 0xFFFFFFFF) {
EPRINTF("Unable to get table index for file.");
return 0;
}
if (!save_fs_list_get_value(&ctx->file_table, index, entry)) {
EPRINTF("Unable to get file entry from index.");
return 0;
}
return 1;
}
void save_open_fat_storage(save_filesystem_ctx_t *ctx, allocation_table_storage_ctx_t *storage_ctx, uint32_t block_index) {
storage_ctx->base_storage = ctx->base_storage;
storage_ctx->fat = &ctx->allocation_table;
storage_ctx->block_size = (uint32_t)ctx->header->block_size;
storage_ctx->initial_block = block_index;
storage_ctx->_length = block_index == 0xFFFFFFFF ? 0 : save_allocation_table_get_list_length(storage_ctx->fat, block_index) * storage_ctx->block_size;
}
void save_filesystem_init(save_filesystem_ctx_t *ctx, void *fat, save_fs_header_t *save_fs_header, fat_header_t *fat_header) {
ctx->allocation_table.base_storage = fat;
ctx->allocation_table.header = fat_header;
ctx->allocation_table.free_list_entry_index = 0;
ctx->header = save_fs_header;
save_open_fat_storage(ctx, &ctx->file_table.directory_table.storage, fat_header->directory_table_block);
save_open_fat_storage(ctx, &ctx->file_table.file_table.storage, fat_header->file_table_block);
ctx->file_table.file_table.free_list_head_index = 0;
ctx->file_table.file_table.used_list_head_index = 1;
ctx->file_table.directory_table.free_list_head_index = 0;
ctx->file_table.directory_table.used_list_head_index = 1;
}
validity_t save_ivfc_validate(hierarchical_integrity_verification_storage_ctx_t *ctx, ivfc_save_hdr_t *ivfc) {
validity_t result = VALIDITY_VALID;
for (unsigned int i = 0; i < ivfc->num_levels - 1 && result != VALIDITY_INVALID; i++) {
integrity_verification_storage_ctx_t *storage = &ctx->integrity_storages[i];
uint64_t block_size = storage->sector_size;
uint32_t block_count = (uint32_t)((storage->_length + block_size - 1) / block_size);
uint8_t *buffer = malloc(block_size);
for (unsigned int j = 0; j < block_count; j++) {
if (ctx->level_validities[ivfc->num_levels - 2][j] == VALIDITY_UNCHECKED) {
uint32_t to_read = storage->_length - block_size * j < block_size ? storage->_length - block_size * j : block_size;
save_ivfc_storage_read(storage, buffer, block_size * j, to_read, 1);
}
if (ctx->level_validities[ivfc->num_levels - 2][j] == VALIDITY_INVALID) {
result = VALIDITY_INVALID;
break;
}
}
free(buffer);
}
return result;
}
void save_ivfc_set_level_validities(hierarchical_integrity_verification_storage_ctx_t *ctx, ivfc_save_hdr_t *ivfc) {
for (unsigned int i = 0; i < ivfc->num_levels - 1; i++) {
validity_t level_validity = VALIDITY_VALID;
for (unsigned int j = 0; j < ctx->integrity_storages[i].sector_count; j++) {
if (ctx->level_validities[i][j] == VALIDITY_INVALID) {
level_validity = VALIDITY_INVALID;
break;
}
if (ctx->level_validities[i][j] == VALIDITY_UNCHECKED && level_validity != VALIDITY_INVALID) {
level_validity = VALIDITY_UNCHECKED;
}
}
ctx->levels[i].hash_validity = level_validity;
}
}
validity_t save_filesystem_verify(save_ctx_t *ctx) {
validity_t journal_validity = save_ivfc_validate(&ctx->core_data_ivfc_storage, &ctx->header.data_ivfc_header);
save_ivfc_set_level_validities(&ctx->core_data_ivfc_storage, &ctx->header.data_ivfc_header);
if (!ctx->fat_ivfc_storage.levels[0].save_ctx) return journal_validity;
validity_t fat_validity = save_ivfc_validate(&ctx->fat_ivfc_storage, &ctx->header.fat_ivfc_header);
save_ivfc_set_level_validities(&ctx->fat_ivfc_storage, &ctx->header.fat_ivfc_header);
if (journal_validity != VALIDITY_VALID) return journal_validity;
if (fat_validity != VALIDITY_VALID) return fat_validity;
return journal_validity;
}
bool save_process(save_ctx_t *ctx) {
/* Try to parse Header A. */
f_lseek(ctx->file, 0);
if (f_read(ctx->file, &ctx->header, sizeof(ctx->header), NULL)) {
EPRINTF("Failed to read save header!\n");
return false;
}
if (!save_process_header(ctx) || (ctx->header_hash_validity == VALIDITY_INVALID)) {
/* Try to parse Header B. */
f_lseek(ctx->file, 0x4000);
if (f_read(ctx->file, &ctx->header, sizeof(ctx->header), NULL)) {
EPRINTF("Failed to read save header!\n");
return false;
}
if (!save_process_header(ctx) || (ctx->header_hash_validity == VALIDITY_INVALID)) {
EPRINTF("Error: Save header is invalid!");
return false;
}
}
unsigned char cmac[0x10] = {};
se_aes_key_set(10, ctx->save_mac_key, 0x10);
se_aes_cmac(10, cmac, 0x10, &ctx->header.layout, sizeof(ctx->header.layout));
if (memcmp(cmac, &ctx->header.cmac, 0x10) == 0) {
ctx->header_cmac_validity = VALIDITY_VALID;
} else {
ctx->header_cmac_validity = VALIDITY_INVALID;
}
/* Initialize remap storages. */
ctx->data_remap_storage.type = STORAGE_BYTES;
ctx->data_remap_storage.base_storage_offset = ctx->header.layout.file_map_data_offset;
ctx->data_remap_storage.header = &ctx->header.main_remap_header;
ctx->data_remap_storage.map_entries = malloc(sizeof(remap_entry_ctx_t) * ctx->data_remap_storage.header->map_entry_count);
ctx->data_remap_storage.file = ctx->file;
f_lseek(ctx->file, ctx->header.layout.file_map_entry_offset);
for (unsigned int i = 0; i < ctx->data_remap_storage.header->map_entry_count; i++) {
f_read(ctx->file, &ctx->data_remap_storage.map_entries[i], 0x20, NULL);
ctx->data_remap_storage.map_entries[i].physical_offset_end = ctx->data_remap_storage.map_entries[i].physical_offset + ctx->data_remap_storage.map_entries[i].size;
ctx->data_remap_storage.map_entries[i].virtual_offset_end = ctx->data_remap_storage.map_entries[i].virtual_offset + ctx->data_remap_storage.map_entries[i].size;
}
/* Initialize data remap storage. */
ctx->data_remap_storage.segments = save_remap_init_segments(ctx->data_remap_storage.header, ctx->data_remap_storage.map_entries, ctx->data_remap_storage.header->map_entry_count);
/* Initialize duplex storage. */
ctx->duplex_layers[0].data_a = (uint8_t *)&ctx->header + ctx->header.layout.duplex_master_offset_a;
ctx->duplex_layers[0].data_b = (uint8_t *)&ctx->header + ctx->header.layout.duplex_master_offset_b;
memcpy(&ctx->duplex_layers[0].info, &ctx->header.duplex_header.layers[0], sizeof(duplex_info_t));
ctx->duplex_layers[1].data_a = malloc(ctx->header.layout.duplex_l1_size);
save_remap_read(&ctx->data_remap_storage, ctx->duplex_layers[1].data_a, ctx->header.layout.duplex_l1_offset_a, ctx->header.layout.duplex_l1_size);
ctx->duplex_layers[1].data_b = malloc(ctx->header.layout.duplex_l1_size);
save_remap_read(&ctx->data_remap_storage, ctx->duplex_layers[1].data_b, ctx->header.layout.duplex_l1_offset_b, ctx->header.layout.duplex_l1_size);
memcpy(&ctx->duplex_layers[1].info, &ctx->header.duplex_header.layers[1], sizeof(duplex_info_t));
ctx->duplex_layers[2].data_a = malloc(ctx->header.layout.duplex_data_size);
save_remap_read(&ctx->data_remap_storage, ctx->duplex_layers[2].data_a, ctx->header.layout.duplex_data_offset_a, ctx->header.layout.duplex_data_size);
ctx->duplex_layers[2].data_b = malloc(ctx->header.layout.duplex_data_size);
save_remap_read(&ctx->data_remap_storage, ctx->duplex_layers[2].data_b, ctx->header.layout.duplex_data_offset_b, ctx->header.layout.duplex_data_size);
memcpy(&ctx->duplex_layers[2].info, &ctx->header.duplex_header.layers[2], sizeof(duplex_info_t));
/* Initialize hierarchical duplex storage. */
uint8_t *bitmap = ctx->header.layout.duplex_index == 1 ? ctx->duplex_layers[0].data_b : ctx->duplex_layers[0].data_a;
save_duplex_storage_init(&ctx->duplex_storage.layers[0], &ctx->duplex_layers[1], bitmap, ctx->header.layout.duplex_master_size);
ctx->duplex_storage.layers[0]._length = ctx->header.layout.duplex_l1_size;
bitmap = malloc(ctx->duplex_storage.layers[0]._length);
save_duplex_storage_read(&ctx->duplex_storage.layers[0], bitmap, 0, ctx->duplex_storage.layers[0]._length);
save_duplex_storage_init(&ctx->duplex_storage.layers[1], &ctx->duplex_layers[2], bitmap, ctx->duplex_storage.layers[0]._length);
ctx->duplex_storage.layers[1]._length = ctx->header.layout.duplex_data_size;
ctx->duplex_storage.data_layer = ctx->duplex_storage.layers[1];
/* Initialize meta remap storage. */
ctx->meta_remap_storage.type = STORAGE_DUPLEX;
ctx->meta_remap_storage.duplex = &ctx->duplex_storage.data_layer;
ctx->meta_remap_storage.header = &ctx->header.meta_remap_header;
ctx->meta_remap_storage.map_entries = malloc(sizeof(remap_entry_ctx_t) * ctx->meta_remap_storage.header->map_entry_count);
ctx->meta_remap_storage.file = ctx->file;
f_lseek(ctx->file, ctx->header.layout.meta_map_entry_offset);
for (unsigned int i = 0; i < ctx->meta_remap_storage.header->map_entry_count; i++) {
f_read(ctx->file, &ctx->meta_remap_storage.map_entries[i], 0x20, NULL);
ctx->meta_remap_storage.map_entries[i].physical_offset_end = ctx->meta_remap_storage.map_entries[i].physical_offset + ctx->meta_remap_storage.map_entries[i].size;
ctx->meta_remap_storage.map_entries[i].virtual_offset_end = ctx->meta_remap_storage.map_entries[i].virtual_offset + ctx->meta_remap_storage.map_entries[i].size;
}
ctx->meta_remap_storage.segments = save_remap_init_segments(ctx->meta_remap_storage.header, ctx->meta_remap_storage.map_entries, ctx->meta_remap_storage.header->map_entry_count);
/* Initialize journal map. */
ctx->journal_map_info.map_storage = malloc(ctx->header.layout.journal_map_table_size);
save_remap_read(&ctx->meta_remap_storage, ctx->journal_map_info.map_storage, ctx->header.layout.journal_map_table_offset, ctx->header.layout.journal_map_table_size);
/* Initialize journal storage. */
ctx->journal_storage.header = &ctx->header.journal_header;
ctx->journal_storage.journal_data_offset = ctx->header.layout.journal_data_offset;
ctx->journal_storage._length = ctx->journal_storage.header->total_size - ctx->journal_storage.header->journal_size;
ctx->journal_storage.file = ctx->file;
ctx->journal_storage.map.header = &ctx->header.map_header;
ctx->journal_storage.map.map_storage = ctx->journal_map_info.map_storage;
ctx->journal_storage.map.entries = malloc(sizeof(journal_map_entry_t) * ctx->journal_storage.map.header->main_data_block_count);
uint32_t *pos = (uint32_t *)ctx->journal_storage.map.map_storage;
for (unsigned int i = 0; i < ctx->journal_storage.map.header->main_data_block_count; i++) {
ctx->journal_storage.map.entries[i].virtual_index = i;
ctx->journal_storage.map.entries[i].physical_index = *pos & 0x7FFFFFFF;
pos += 2;
}
ctx->journal_storage.block_size = ctx->journal_storage.header->block_size;
ctx->journal_storage._length = ctx->journal_storage.header->total_size - ctx->journal_storage.header->journal_size;
/* Initialize core IVFC storage. */
for (unsigned int i = 0; i < 5; i++) {
ctx->core_data_ivfc_storage.levels[i].save_ctx = ctx;
}
save_ivfc_storage_init(&ctx->core_data_ivfc_storage, ctx->header.layout.ivfc_master_hash_offset_a, &ctx->header.data_ivfc_header);
/* Initialize FAT storage. */
if (ctx->header.layout.version < 0x50000) {
ctx->fat_storage = malloc(ctx->header.layout.fat_size);
save_remap_read(&ctx->meta_remap_storage, ctx->fat_storage, ctx->header.layout.fat_offset, ctx->header.layout.fat_size);
} else {
for (unsigned int i = 0; i < 5; i++) {
ctx->fat_ivfc_storage.levels[i].save_ctx = ctx;
}
save_ivfc_storage_init(&ctx->fat_ivfc_storage, ctx->header.layout.fat_ivfc_master_hash_a, &ctx->header.fat_ivfc_header);
ctx->fat_storage = malloc(ctx->fat_ivfc_storage._length);
save_remap_read(&ctx->meta_remap_storage, ctx->fat_storage, ctx->header.fat_ivfc_header.level_headers[ctx->header.fat_ivfc_header.num_levels - 2].logical_offset, ctx->fat_ivfc_storage._length);
}
if (ctx->tool_ctx.action & ACTION_VERIFY) {
save_filesystem_verify(ctx);
}
/* Initialize core save filesystem. */
ctx->save_filesystem_core.base_storage = &ctx->core_data_ivfc_storage;
save_filesystem_init(&ctx->save_filesystem_core, ctx->fat_storage, &ctx->header.save_header, &ctx->header.fat_header);
return true;
}
bool save_process_header(save_ctx_t *ctx) {
if (ctx->header.layout.magic != MAGIC_DISF || ctx->header.duplex_header.magic != MAGIC_DPFS ||
ctx->header.data_ivfc_header.magic != MAGIC_IVFC || ctx->header.journal_header.magic != MAGIC_JNGL ||
ctx->header.save_header.magic != MAGIC_SAVE || ctx->header.main_remap_header.magic != MAGIC_RMAP ||
ctx->header.meta_remap_header.magic != MAGIC_RMAP)
{
EPRINTF("Error: Save header is corrupt!");
return false;
}
ctx->data_ivfc_master = (uint8_t *)&ctx->header + ctx->header.layout.ivfc_master_hash_offset_a;
ctx->fat_ivfc_master = (uint8_t *)&ctx->header + ctx->header.layout.fat_ivfc_master_hash_a;
uint8_t hash[0x20];
se_calc_sha256(hash, &ctx->header.duplex_header, 0x3D00);
ctx->header_hash_validity = memcmp(hash, ctx->header.layout.hash, 0x20) == 0 ? VALIDITY_VALID : VALIDITY_INVALID;
ctx->header.data_ivfc_header.num_levels = 5;
if (ctx->header.layout.version >= 0x50000) {
ctx->header.fat_ivfc_header.num_levels = 4;
}
return true;
}
void save_free_contexts(save_ctx_t *ctx) {
for (unsigned int i = 0; i < ctx->data_remap_storage.header->map_segment_count; i++) {
free(ctx->data_remap_storage.segments[i].entries);
}
free(ctx->data_remap_storage.segments);
for (unsigned int i = 0; i < ctx->meta_remap_storage.header->map_segment_count; i++) {
free(ctx->meta_remap_storage.segments[i].entries);
}
free(ctx->meta_remap_storage.segments);
free(ctx->data_remap_storage.map_entries);
free(ctx->meta_remap_storage.map_entries);
free(ctx->duplex_storage.layers[0].bitmap.bitmap);
free(ctx->duplex_storage.layers[1].bitmap.bitmap);
free(ctx->duplex_storage.layers[1].bitmap_storage);
for (unsigned int i = 1; i < 3; i++) {
free(ctx->duplex_layers[i].data_a);
free(ctx->duplex_layers[i].data_b);
}
free(ctx->journal_map_info.map_storage);
free(ctx->journal_storage.map.entries);
for (unsigned int i = 0; i < ctx->header.data_ivfc_header.num_levels - 1; i++) {
free(ctx->core_data_ivfc_storage.integrity_storages[i].block_validities);
}
free(ctx->core_data_ivfc_storage.level_validities);
if (ctx->header.layout.version >= 0x50000) {
for (unsigned int i = 0; i < ctx->header.fat_ivfc_header.num_levels - 1; i++) {
free(ctx->fat_ivfc_storage.integrity_storages[i].block_validities);
}
}
free(ctx->fat_ivfc_storage.level_validities);
free(ctx->fat_storage);
}

View File

@ -1,526 +0,0 @@
#ifndef _SAVE_H
#define _SAVE_H
#include <stddef.h>
#include <stdint.h>
#include <libs/fatfs/ff.h>
#define SAVE_HEADER_SIZE 0x4000
#define SAVE_FAT_ENTRY_SIZE 8
#define SAVE_FS_LIST_MAX_NAME_LENGTH 0x40
#define SAVE_FS_LIST_ENTRY_SIZE 0x60
#define IVFC_MAX_LEVEL 6
#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 enum {
VALIDITY_UNCHECKED = 0,
VALIDITY_INVALID,
VALIDITY_VALID
} validity_t;
typedef struct save_ctx_t save_ctx_t;
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 allocation_table_offset;
uint32_t allocation_table_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 remap_segment_ctx_t remap_segment_ctx_t;
typedef struct remap_entry_ctx_t remap_entry_ctx_t;
#pragma pack(push, 1)
struct remap_entry_ctx_t {
uint64_t virtual_offset;
uint64_t physical_offset;
uint64_t size;
uint32_t alignment;
uint32_t _0x1C;
uint64_t virtual_offset_end;
uint64_t physical_offset_end;
remap_segment_ctx_t *segment;
remap_entry_ctx_t *next;
};
#pragma pack(pop)
struct remap_segment_ctx_t{
uint64_t offset;
uint64_t length;
remap_entry_ctx_t **entries;
uint64_t entry_count;
};
typedef struct {
uint8_t *data;
uint8_t *bitmap;
} duplex_bitmap_t;
typedef struct {
uint32_t block_size;
uint8_t *bitmap_storage;
uint8_t *data_a;
uint8_t *data_b;
duplex_bitmap_t bitmap;
uint64_t _length;
} duplex_storage_ctx_t;
enum base_storage_type {
STORAGE_BYTES = 0,
STORAGE_DUPLEX = 1,
STORAGE_REMAP = 2,
STORAGE_JOURNAL = 3
};
typedef struct {
remap_header_t *header;
remap_entry_ctx_t *map_entries;
remap_segment_ctx_t *segments;
enum base_storage_type type;
uint64_t base_storage_offset;
duplex_storage_ctx_t *duplex;
FIL *file;
} remap_storage_ctx_t;
typedef struct {
uint64_t program_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;
typedef struct {
uint64_t logical_offset;
uint64_t hash_data_size;
uint32_t block_size;
uint32_t reserved;
} ivfc_level_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;
#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 data_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;
uint8_t _0xB98[0x3468];
} save_header_t;
#pragma pack(pop)
typedef struct {
duplex_storage_ctx_t layers[2];
duplex_storage_ctx_t data_layer;
uint64_t _length;
} hierarchical_duplex_storage_ctx_t;
typedef struct {
uint8_t *data_a;
uint8_t *data_b;
duplex_info_t info;
} duplex_fs_layer_info_t;
typedef struct {
uint8_t *map_storage;
uint8_t *physical_block_bitmap;
uint8_t *virtual_block_bitmap;
uint8_t *free_block_bitmap;
} journal_map_params_t;
typedef struct {
uint32_t physical_index;
uint32_t virtual_index;
} journal_map_entry_t;
typedef struct {
journal_map_header_t *header;
journal_map_entry_t *entries;
uint8_t *map_storage;
} journal_map_ctx_t;
typedef struct {
journal_map_ctx_t map;
journal_header_t *header;
uint32_t block_size;
uint64_t journal_data_offset;
uint64_t _length;
FIL *file;
} journal_storage_ctx_t;
typedef struct {
uint64_t data_offset;
uint64_t data_size;
uint64_t hash_offset;
uint32_t hash_block_size;
validity_t hash_validity;
enum base_storage_type type;
save_ctx_t *save_ctx;
} ivfc_level_save_ctx_t;
typedef struct {
ivfc_level_save_ctx_t *data;
uint32_t block_size;
uint8_t salt[0x20];
} integrity_verification_info_ctx_t;
typedef struct integrity_verification_storage_ctx_t integrity_verification_storage_ctx_t;
struct integrity_verification_storage_ctx_t {
ivfc_level_save_ctx_t *hash_storage;
ivfc_level_save_ctx_t *base_storage;
validity_t *block_validities;
uint8_t salt[0x20];
uint32_t sector_size;
uint32_t sector_count;
uint64_t _length;
integrity_verification_storage_ctx_t *next_level;
};
typedef struct {
ivfc_level_save_ctx_t levels[5];
ivfc_level_save_ctx_t *data_level;
validity_t **level_validities;
uint64_t _length;
integrity_verification_storage_ctx_t integrity_storages[4];
} hierarchical_integrity_verification_storage_ctx_t;
typedef struct {
uint32_t prev;
uint32_t next;
} allocation_table_entry_t;
typedef struct {
uint32_t free_list_entry_index;
void *base_storage;
fat_header_t *header;
} allocation_table_ctx_t;
typedef struct {
hierarchical_integrity_verification_storage_ctx_t *base_storage;
uint32_t block_size;
uint32_t initial_block;
allocation_table_ctx_t *fat;
uint64_t _length;
} allocation_table_storage_ctx_t;
typedef struct {
allocation_table_ctx_t *fat;
uint32_t virtual_block;
uint32_t physical_block;
uint32_t current_segment_size;
uint32_t next_block;
uint32_t prev_block;
} allocation_table_iterator_ctx_t;
typedef struct {
char name[SAVE_FS_LIST_MAX_NAME_LENGTH];
uint32_t parent;
} save_entry_key_t;
#pragma pack(push, 1)
typedef struct {
uint32_t start_block;
uint64_t length;
uint32_t _0xC[2];
} save_file_info_t;
#pragma pack(pop)
#pragma pack(push, 1)
typedef struct {
uint32_t next_directory;
uint32_t next_file;
uint32_t _0x8[3];
} save_find_position_t;
#pragma pack(pop)
#pragma pack(push, 1)
typedef struct {
uint32_t next_sibling;
union { /* Save table entry type. Size = 0x14. */
save_file_info_t save_file_info;
save_find_position_t save_find_position;
};
} save_table_entry_t;
#pragma pack(pop)
#pragma pack(push, 1)
typedef struct {
uint32_t parent;
char name[SAVE_FS_LIST_MAX_NAME_LENGTH];
save_table_entry_t value;
uint32_t next;
} save_fs_list_entry_t;
#pragma pack(pop)
typedef struct {
uint32_t free_list_head_index;
uint32_t used_list_head_index;
allocation_table_storage_ctx_t storage;
uint32_t capacity;
} save_filesystem_list_ctx_t;
typedef struct {
save_filesystem_list_ctx_t file_table;
save_filesystem_list_ctx_t directory_table;
} hierarchical_save_file_table_ctx_t;
typedef struct {
hierarchical_integrity_verification_storage_ctx_t *base_storage;
allocation_table_ctx_t allocation_table;
save_fs_header_t *header;
hierarchical_save_file_table_ctx_t file_table;
} save_filesystem_ctx_t;
#define ACTION_VERIFY (1<<2)
struct save_ctx_t {
save_header_t header;
FIL *file;
struct {
FIL *file;
uint32_t action;
} tool_ctx;
validity_t header_cmac_validity;
validity_t header_hash_validity;
uint8_t *data_ivfc_master;
uint8_t *fat_ivfc_master;
remap_storage_ctx_t data_remap_storage;
remap_storage_ctx_t meta_remap_storage;
duplex_fs_layer_info_t duplex_layers[3];
hierarchical_duplex_storage_ctx_t duplex_storage;
journal_storage_ctx_t journal_storage;
journal_map_params_t journal_map_info;
hierarchical_integrity_verification_storage_ctx_t core_data_ivfc_storage;
hierarchical_integrity_verification_storage_ctx_t fat_ivfc_storage;
uint8_t *fat_storage;
save_filesystem_ctx_t save_filesystem_core;
uint8_t save_mac_key[0x10];
};
static inline uint32_t allocation_table_entry_index_to_block(uint32_t entry_index) {
return entry_index - 1;
}
static inline uint32_t allocation_table_block_to_entry_index(uint32_t block_index) {
return block_index + 1;
}
static inline int allocation_table_get_prev(allocation_table_entry_t *entry) {
return entry->prev & 0x7FFFFFFF;
}
static inline int allocation_table_get_next(allocation_table_entry_t *entry) {
return entry->next & 0x7FFFFFFF;
}
static inline int allocation_table_is_list_start(allocation_table_entry_t *entry) {
return entry->prev == 0x80000000;
}
static inline int allocation_table_is_list_end(allocation_table_entry_t *entry) {
return (entry->next & 0x7FFFFFFF) == 0;
}
static inline bool allocation_table_is_multi_block_segment(allocation_table_entry_t *entry) {
return entry->next & 0x80000000;
}
static inline void allocation_table_make_multi_block_segment(allocation_table_entry_t *entry) {
entry->next |= 0x80000000;
}
static inline void allocation_table_make_single_block_segment(allocation_table_entry_t *entry) {
entry->next &= 0x7FFFFFFF;
}
static inline bool allocation_table_is_single_block_segment(allocation_table_entry_t *entry) {
return (entry->next & 0x80000000) == 0;
}
static inline void allocation_table_make_list_start(allocation_table_entry_t *entry) {
entry->prev = 0x80000000;
}
static inline bool allocation_table_is_range_entry(allocation_table_entry_t *entry) {
return (entry->prev & 0x80000000) != 0 && entry->prev != 0x80000000;
}
static inline void allocation_table_make_range_entry(allocation_table_entry_t *entry) {
entry->prev |= 0x80000000;
}
static inline void allocation_table_set_next(allocation_table_entry_t *entry, int val) {
entry->next = (entry->next & 0x80000000) | val;
}
static inline void allocation_table_set_prev(allocation_table_entry_t *entry, int val) {
entry->prev = val;
}
static inline void allocation_table_set_range(allocation_table_entry_t *entry, int start_index, int end_index) {
entry->next = end_index;
entry->prev = start_index;
allocation_table_make_range_entry(entry);
}
static inline allocation_table_entry_t *save_allocation_table_read_entry(allocation_table_ctx_t *ctx, uint32_t entry_index) {
return (allocation_table_entry_t *)((uint8_t *)ctx->base_storage + entry_index * SAVE_FAT_ENTRY_SIZE);
}
static inline uint32_t save_allocation_table_get_free_list_entry_index(allocation_table_ctx_t *ctx) {
return allocation_table_get_next(save_allocation_table_read_entry(ctx, ctx->free_list_entry_index));
}
static inline uint32_t save_allocation_table_get_free_list_block_index(allocation_table_ctx_t *ctx) {
return allocation_table_entry_index_to_block(save_allocation_table_get_free_list_entry_index(ctx));
}
bool save_process(save_ctx_t *ctx);
bool save_process_header(save_ctx_t *ctx);
void save_free_contexts(save_ctx_t *ctx);
void save_open_fat_storage(save_filesystem_ctx_t *ctx, allocation_table_storage_ctx_t *storage_ctx, uint32_t block_index);
uint32_t save_allocation_table_storage_read(allocation_table_storage_ctx_t *ctx, void *buffer, uint64_t offset, size_t count);
int save_fs_list_get_value(save_filesystem_list_ctx_t *ctx, uint32_t index, save_fs_list_entry_t *value);
int save_hierarchical_file_table_get_file_entry_by_path(hierarchical_save_file_table_ctx_t *ctx, const char *path, save_fs_list_entry_t *entry);
#endif