mirror of
https://github.com/Decscots/Lockpick_RCM
synced 2024-11-22 08:39:25 +00:00
Huge savedata driver refactor
This commit is contained in:
parent
bd134cf670
commit
04378b322d
44 changed files with 5485 additions and 1355 deletions
281
bdk/libs/nx_savedata/allocation_table.c
Normal file
281
bdk/libs/nx_savedata/allocation_table.c
Normal 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);
|
||||||
|
}
|
171
bdk/libs/nx_savedata/allocation_table.h
Normal file
171
bdk/libs/nx_savedata/allocation_table.h
Normal 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
|
111
bdk/libs/nx_savedata/allocation_table_iterator.c
Normal file
111
bdk/libs/nx_savedata/allocation_table_iterator.c
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
56
bdk/libs/nx_savedata/allocation_table_iterator.h
Normal file
56
bdk/libs/nx_savedata/allocation_table_iterator.h
Normal 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
|
158
bdk/libs/nx_savedata/allocation_table_storage.c
Normal file
158
bdk/libs/nx_savedata/allocation_table_storage.c
Normal 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;
|
||||||
|
}
|
60
bdk/libs/nx_savedata/allocation_table_storage.h
Normal file
60
bdk/libs/nx_savedata/allocation_table_storage.h
Normal 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
|
227
bdk/libs/nx_savedata/cached_storage.c
Normal file
227
bdk/libs/nx_savedata/cached_storage.c
Normal 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;
|
||||||
|
}
|
68
bdk/libs/nx_savedata/cached_storage.h
Normal file
68
bdk/libs/nx_savedata/cached_storage.h
Normal 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
|
60
bdk/libs/nx_savedata/directory_entry.h
Normal file
60
bdk/libs/nx_savedata/directory_entry.h
Normal 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
|
108
bdk/libs/nx_savedata/duplex_storage.c
Normal file
108
bdk/libs/nx_savedata/duplex_storage.c
Normal 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;
|
||||||
|
}
|
76
bdk/libs/nx_savedata/duplex_storage.h
Normal file
76
bdk/libs/nx_savedata/duplex_storage.h
Normal 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
|
40
bdk/libs/nx_savedata/fs_int64.h
Normal file
40
bdk/libs/nx_savedata/fs_int64.h
Normal 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
|
237
bdk/libs/nx_savedata/header.h
Normal file
237
bdk/libs/nx_savedata/header.h
Normal 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
|
120
bdk/libs/nx_savedata/hierarchical_duplex_storage.c
Normal file
120
bdk/libs/nx_savedata/hierarchical_duplex_storage.c
Normal 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;
|
||||||
|
}
|
61
bdk/libs/nx_savedata/hierarchical_duplex_storage.h
Normal file
61
bdk/libs/nx_savedata/hierarchical_duplex_storage.h
Normal 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
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
483
bdk/libs/nx_savedata/hierarchical_save_file_table.c
Normal file
483
bdk/libs/nx_savedata/hierarchical_save_file_table.c
Normal 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, ¤t_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, ¤t_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, ¤t_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;
|
||||||
|
}
|
||||||
|
|
59
bdk/libs/nx_savedata/hierarchical_save_file_table.h
Normal file
59
bdk/libs/nx_savedata/hierarchical_save_file_table.h
Normal 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
|
155
bdk/libs/nx_savedata/integrity_verification_storage.c
Normal file
155
bdk/libs/nx_savedata/integrity_verification_storage.c
Normal 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;
|
||||||
|
}
|
60
bdk/libs/nx_savedata/integrity_verification_storage.h
Normal file
60
bdk/libs/nx_savedata/integrity_verification_storage.h
Normal 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
|
63
bdk/libs/nx_savedata/journal_map.c
Normal file
63
bdk/libs/nx_savedata/journal_map.c
Normal 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);
|
||||||
|
}
|
84
bdk/libs/nx_savedata/journal_map.h
Normal file
84
bdk/libs/nx_savedata/journal_map.h
Normal 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
|
92
bdk/libs/nx_savedata/journal_storage.c
Normal file
92
bdk/libs/nx_savedata/journal_storage.c
Normal 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;
|
||||||
|
}
|
69
bdk/libs/nx_savedata/journal_storage.h
Normal file
69
bdk/libs/nx_savedata/journal_storage.h
Normal 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
|
84
bdk/libs/nx_savedata/path_parser.c
Normal file
84
bdk/libs/nx_savedata/path_parser.c
Normal 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, ¤t_len);
|
||||||
|
memcpy(name, current, current_len);
|
||||||
|
return success;
|
||||||
|
}
|
59
bdk/libs/nx_savedata/path_parser.h
Normal file
59
bdk/libs/nx_savedata/path_parser.h
Normal 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
|
160
bdk/libs/nx_savedata/remap_storage.c
Normal file
160
bdk/libs/nx_savedata/remap_storage.c
Normal 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;
|
||||||
|
}
|
104
bdk/libs/nx_savedata/remap_storage.h
Normal file
104
bdk/libs/nx_savedata/remap_storage.h
Normal 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
306
bdk/libs/nx_savedata/save.c
Normal 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
154
bdk/libs/nx_savedata/save.h
Normal 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
|
89
bdk/libs/nx_savedata/save_data_directory.c
Normal file
89
bdk/libs/nx_savedata/save_data_directory.c
Normal 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);
|
||||||
|
}
|
56
bdk/libs/nx_savedata/save_data_directory.h
Normal file
56
bdk/libs/nx_savedata/save_data_directory.h
Normal 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
|
132
bdk/libs/nx_savedata/save_data_file.c
Normal file
132
bdk/libs/nx_savedata/save_data_file.c
Normal 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;
|
||||||
|
}
|
62
bdk/libs/nx_savedata/save_data_file.h
Normal file
62
bdk/libs/nx_savedata/save_data_file.h
Normal 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
|
142
bdk/libs/nx_savedata/save_data_file_system_core.c
Normal file
142
bdk/libs/nx_savedata/save_data_file_system_core.c
Normal 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;
|
||||||
|
}
|
79
bdk/libs/nx_savedata/save_data_file_system_core.h
Normal file
79
bdk/libs/nx_savedata/save_data_file_system_core.h
Normal 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
|
66
bdk/libs/nx_savedata/save_fs_entry.h
Normal file
66
bdk/libs/nx_savedata/save_fs_entry.h
Normal 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
|
300
bdk/libs/nx_savedata/save_fs_list.c
Normal file
300
bdk/libs/nx_savedata/save_fs_list.c
Normal 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, ¤t_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;
|
||||||
|
}
|
99
bdk/libs/nx_savedata/save_fs_list.h
Normal file
99
bdk/libs/nx_savedata/save_fs_list.h
Normal 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
|
241
bdk/libs/nx_savedata/storage.c
Normal file
241
bdk/libs/nx_savedata/storage.c
Normal 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;
|
||||||
|
}
|
169
bdk/libs/nx_savedata/storage.h
Normal file
169
bdk/libs/nx_savedata/storage.h
Normal 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
|
|
@ -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);
|
|
||||||
}
|
|
|
@ -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
|
|
Loading…
Reference in a new issue