diff --git a/psvpfsparser/FilesDbParser.h b/psvpfsparser/FilesDbParser.h index 2bd03da..bf64cad 100644 --- a/psvpfsparser/FilesDbParser.h +++ b/psvpfsparser/FilesDbParser.h @@ -47,7 +47,7 @@ struct sce_ng_pfs_header_t std::uint64_t tailSize; // size of data after this header std::uint64_t total_sz; // is 0 std::uint8_t root_icv[0x14]; // 0x38 hmac-sha1 of (pageSize - 4) of page (pointed by root_icv_page_number) with secret derived from klicensee - std::uint8_t header_icv[0x14]; // 0x4C hmac-sha1 of 0x16 bytes of header with secret derived from klicensee + std::uint8_t header_icv[0x14]; // 0x4C hmac-sha1 of 0x160 bytes of header with secret derived from klicensee std::uint8_t rsa_sig0[0x100]; std::uint8_t rsa_sig1[0x100]; std::uint8_t padding[0x1A0]; diff --git a/psvpfsparser/MerkleTree.hpp b/psvpfsparser/MerkleTree.hpp deleted file mode 100644 index c971e17..0000000 --- a/psvpfsparser/MerkleTree.hpp +++ /dev/null @@ -1,304 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -//=============== types =========================== - -template -class merkle_tree_node -{ -public: - std::shared_ptr m_parent; - std::shared_ptr m_left; - std::shared_ptr m_right; - -public: - //used for propagating sector index - std::uint32_t m_index; - //useful for aggregating nodes by level - std::uint32_t m_depth; - -public: - //used to store any other user context linked to this node - T m_context; - -public: - merkle_tree_node() - : m_parent(nullptr), - m_left(nullptr), - m_right(nullptr), - m_index(0) - { - } - - bool isLeaf() - { - return !m_left && !m_right; - } -}; - -template -class merkle_tree -{ -public: - //number of nodes is used to make iterating through the tree easier - std::uint32_t nNodes; - //number of leaves in the tree - can be usefull - std::uint32_t nLeaves; - //root of the merkle tree - std::shared_ptr > root; -}; - -//================ generator ========================== - -//this generates an empty merkle tree with specified number of leaves -//merkle tree is always a full tree -template -std::shared_ptr > generate_merkle_tree(std::uint32_t nSectors) -{ - // number of nodes is N = 2 * L - 1 if L is number of leaves - // in case of merkle trees - leaves are sector hashes - // I am not sure but merkle trees are probably always full trees - // meaning that each node has 2 children - std::uint32_t nNodesMax = nSectors * 2 - 1; - std::uint32_t nNodes = 1; - std::uint32_t depth = 0; - - std::vector > > children; - std::vector > > children_temp; - - std::shared_ptr > root = std::make_shared >(); - root->m_depth = depth++; - children.push_back(root); - - //this is a non recoursive algorithm that iterates through merkle tree - //level by level going from left to right, from top to bottom - while(nNodes != nNodesMax) - { - for(auto c : children) - { - c->m_left = std::make_shared >(); - c->m_left->m_parent = c; - c->m_left->m_depth = depth; - children_temp.push_back(c->m_left); - nNodes++; - if(nNodes == nNodesMax) - throw std::runtime_error("Not a full binary tree"); - - c->m_right = std::make_shared >(); - c->m_right->m_parent = c; - c->m_right->m_depth = depth; - children_temp.push_back(c->m_right); - nNodes++; - if(nNodes == nNodesMax) - break; - - //if algo exits here it means we have unbalanced tree (full tree with last level not completely full) - //this is not important, just for note - } - - //move deeper one level - children.assign(children_temp.begin(), children_temp.end()); - children_temp.clear(); - depth++; - } - - //return a merkle tree - std::shared_ptr > mkt = std::make_shared >(); - mkt->nNodes = nNodesMax; - mkt->nLeaves = nSectors; - mkt->root = root; - return mkt; -} - -//================= walkers ========================= - -template -struct merkle_node_walker -{ - typedef int (type)(std::shared_ptr > node, void* ctx); -}; - -//this functions walks through tree nodes from top to bottom from left to right -//this is non recoursive walk that goes level by level in depth -template -int walk_tree(std::shared_ptr > mkt, typename merkle_node_walker::type* wlk, void* ctx) -{ - std::uint32_t nNodes = 1; - - std::vector > > children; - std::vector > > children_temp; - - children.push_back(mkt->root); - if(wlk(mkt->root, ctx) < 0) - return 0; - - //this is a non recoursive algorithm that iterates through merkle tree - //level by level going from left to right, from top to bottom - while(nNodes != mkt->nNodes) - { - for(auto c : children) - { - if(wlk(c->m_left, ctx) < 0) - return 0; - children_temp.push_back(c->m_left); - nNodes++; - if(nNodes == mkt->nNodes) - throw std::runtime_error("Not a full binary tree"); - - if(wlk(c->m_right, ctx) < 0) - return 0; - children_temp.push_back(c->m_right); - nNodes++; - if(nNodes == mkt->nNodes) - break; - - //if algo exits here it means we have unbalanced tree (full tree with last level not completely full) - //this is not important, just for note - } - - //move deeper one level - children.assign(children_temp.begin(), children_temp.end()); - children_temp.clear(); - } - - return 0; -} - -//walks from top to bottom from left to right in recoursive manner (in depth) -template -int walk_tree_recoursive_forward(const merkle_tree& mkt, typename merkle_node_walker::type* wlk, void* ctx) -{ - std::shared_ptr > currentNode = mkt.root; - - std::stack > > nodeStack; - nodeStack.push(currentNode); - - do - { - do - { - while(true) - { - wlk(currentNode, ctx); - - if(currentNode->isLeaf()) - break; - - nodeStack.push(currentNode); - currentNode = currentNode->m_left; - } - - currentNode = nodeStack.top()->m_right; - nodeStack.pop(); - } - while(!nodeStack.empty()); - } - while(!nodeStack.empty()); - - return 0; -} - -//================ index tree ========================== - -//this is a tree walk indexing function that propagates index from top to bottom, from left to right -template -int tree_indexer(std::shared_ptr > node, void* ctx) -{ - if(node->isLeaf()) - return 0; - - int* idx = (int*)ctx; - - //propagate index to left node - node->m_left->m_index = node->m_index; - - //select next index into right node - node->m_right->m_index = (*idx)++; - - return 0; -} - -//this is a tree_indexer wrapper that keeps state locally -template -int index_merkle_tree(std::shared_ptr > mkt) -{ - int index = 1; - walk_tree(mkt, tree_indexer, &index); - return 0; -} - -//================= depth slice tree ========================= - -template -struct depth_mapper_context -{ - typedef std::map > > > type; - typedef std::uint32_t key_type; - typedef std::vector > > value_type; -}; - -template -int depth_mapper(std::shared_ptr > node, void* ctx) -{ - typename depth_mapper_context::type* nodeDepthMap = (typename depth_mapper_context::type*)ctx; - - auto depthEntryIt = nodeDepthMap->find(node->m_depth); - if(depthEntryIt == nodeDepthMap->end()) - { - auto insRes = nodeDepthMap->insert(std::make_pair(node->m_depth, typename depth_mapper_context::value_type())); - depthEntryIt = insRes.first; - } - - depthEntryIt->second.push_back(node); - - return 0; -} - -template -int map_by_depth(std::shared_ptr > mkt, typename depth_mapper_context::type& nodeDepthMap) -{ - walk_tree(mkt, depth_mapper, &nodeDepthMap); - return 0; -} - -//================ bottom top combiner ========================== - -template -struct node_combiner -{ - typedef int(type)(std::shared_ptr > result, std::shared_ptr > left, std::shared_ptr > right, void* ctx); -}; - -template -int bottom_top_walk_combine(std::shared_ptr > mkt, typename node_combiner::type* wlk, void* ctx) -{ - //build depth slice - typename depth_mapper_context::type nodeDepthMap; - map_by_depth(mkt, nodeDepthMap); - - //walk from bottom to top - for(typename depth_mapper_context::type::const_reverse_iterator it = nodeDepthMap.rbegin(); it != nodeDepthMap.rend(); ++it) - { - //walk through each node - for(auto item : it->second) - { - //skip leaves - if(item->isLeaf()) - continue; - - //call walker - wlk(item, item->m_left, item->m_right, ctx); - } - } - - return 0; -} \ No newline at end of file diff --git a/psvpfsparser/PfsFile.cpp b/psvpfsparser/PfsFile.cpp index 566c1a0..eac1bea 100644 --- a/psvpfsparser/PfsFile.cpp +++ b/psvpfsparser/PfsFile.cpp @@ -1,6 +1,5 @@ #include "PfsFile.h" -#include "MerkleTree.hpp" #include "PfsKeyGenerator.h" PfsFile::PfsFile(std::shared_ptr cryptops, std::shared_ptr iF00D, std::ostream& output, @@ -12,18 +11,7 @@ PfsFile::PfsFile(std::shared_ptr cryptops, std::shared_ptr > node, void* ctx) -{ - if(!node->isLeaf()) - return 0; - - std::vector > >* leaves = (std::vector > >*)ctx; - leaves->push_back(node); - return 0; -} - -int PfsFile::init_crypt_ctx(CryptEngineWorkCtx* work_ctx, sig_tbl_t& block, std::uint32_t sector_base, std::uint32_t tail_size, unsigned char* source) const +int PfsFile::init_crypt_ctx(CryptEngineWorkCtx* work_ctx, std::shared_ptr block, std::uint32_t sector_base, std::uint32_t tail_size, unsigned char* source) const { memset(&m_data, 0, sizeof(CryptEngineData)); m_data.klicensee = m_klicensee; @@ -58,62 +46,19 @@ int PfsFile::init_crypt_ctx(CryptEngineWorkCtx* work_ctx, sig_tbl_t& block, std: m_sub_ctx.work_buffer_ofst = (unsigned char*)0; m_sub_ctx.nBlocksOffset = 0; m_sub_ctx.nBlocksTail = 0; - - if(db_type_to_is_unicv(drv_ctx.db_type)) - m_sub_ctx.nBlocks = block.get_header()->get_nSignatures(); //for unicv - number of hashes is equal to number of sectors, so can use get_nSignatures - else - m_sub_ctx.nBlocks = m_table->get_header()->get_numSectors(); //for icv - there are more hashes than sectors (because of merkle tree), so have to use get_numSectors + m_sub_ctx.nBlocks = block->get_header()->get_nSectors(); m_sub_ctx.sector_base = sector_base; m_sub_ctx.dest_offset = 0; m_sub_ctx.tail_size = tail_size; - if(db_type_to_is_unicv(drv_ctx.db_type)) + m_signatureTable.clear(); + m_signatureTable.resize(block->get_header()->get_nSectors() * block->get_header()->get_sigSize()); + std::uint32_t signatureTableOffset = 0; + for (std::uint32_t i = 0; i < block->get_header()->get_nSectors(); i++) { - m_signatureTable.clear(); - m_signatureTable.resize(block.m_signatures.size() * block.get_header()->get_sigSize()); - std::uint32_t signatureTableOffset = 0; - for(auto& s : block.m_signatures) - { - memcpy(m_signatureTable.data() + signatureTableOffset, s.m_data.data(), block.get_header()->get_sigSize()); - signatureTableOffset += block.get_header()->get_sigSize(); - } - } - else - { - //for icv files we need to restore natural order of hashes in hash table (which is the order of sectors in file) - - //create merkle tree for corresponding table - std::shared_ptr > mkt = generate_merkle_tree(m_table->get_header()->get_numSectors()); - index_merkle_tree(mkt); - - //collect leaves - std::vector > > leaves; - walk_tree(mkt, collect_leaf, &leaves); - - if(mkt->nLeaves != leaves.size()) - { - m_output << "Invalid number of leaves collected" << std::endl; - return -1; - } - - std::map naturalHashTable; - - //skip first chunk of hashes that corresponds to nodes of merkle tree (we only need to go through leaves) - for(std::uint32_t i = mkt->nNodes - mkt->nLeaves, j = 0; i < block.m_signatures.size(); i++, j++) - { - naturalHashTable.insert(std::make_pair(leaves[j]->m_index, block.m_signatures[i])); - } - - m_signatureTable.clear(); - m_signatureTable.resize(naturalHashTable.size() * block.get_header()->get_sigSize()); - - std::uint32_t signatureTableOffset = 0; - for(auto& s : naturalHashTable) - { - memcpy(m_signatureTable.data() + signatureTableOffset, s.second.m_data.data(), block.get_header()->get_sigSize()); - signatureTableOffset += block.get_header()->get_sigSize(); - } + memcpy(m_signatureTable.data() + signatureTableOffset, block->get_icv_for_sector(i)->m_data.data(), block->get_header()->get_sigSize()); + signatureTableOffset += block->get_header()->get_sigSize(); } m_sub_ctx.signature_table = m_signatureTable.data(); @@ -147,29 +92,32 @@ int PfsFile::decrypt_icv_file(boost::filesystem::path destination_root) const //do decryption + std::uintmax_t bytes_left = m_filepath.file_size(); + std::uint32_t sector_base = 0; + // icv.db pfs files are padded to the nearest sector boundary // so we need to get the real size from files.db - std::uintmax_t realfileSize = m_file.file.m_info.header.size; - - std::uintmax_t fileSize = m_filepath.file_size(); + std::uint32_t real_bytes_left = m_file.file.m_info.header.size; //in icv files there are more hashes than sectors due to merkle tree - //that is why we have to use get_numHashes() method here //this is different from unicv where it has one has per sector - //we can use get_numSectors() there - //if number of sectors is less than or same to number that fits into single signature page - if(m_table->get_header()->get_numHashes() <= m_table->get_header()->get_binTreeNumMaxAvail()) + // go through each block of sectors + for (std::shared_ptr b : m_table->m_blocks) { - std::vector buffer(static_cast::size_type>(fileSize)); - inputStream.read((char*)buffer.data(), fileSize); - - std::uint32_t tail_size = fileSize % m_table->get_header()->get_fileSectorSize(); - if(tail_size == 0) - tail_size = m_table->get_header()->get_fileSectorSize(); - + std::shared_ptr block = std::dynamic_pointer_cast(b); + + // skip non-leaf pages + if (block->get_page_height() > 0) + continue; + + std::uint32_t num_sectors = block->get_header()->get_nSectors(); + std::uintmax_t read_size = num_sectors * m_table->get_header()->get_fileSectorSize(); + std::vector buffer(read_size); + inputStream.read((char*)buffer.data(), read_size); + CryptEngineWorkCtx work_ctx; - if(init_crypt_ctx(&work_ctx, m_table->m_blocks.front(), 0, tail_size, buffer.data()) < 0) + if(init_crypt_ctx(&work_ctx, block, sector_base, m_table->get_header()->get_fileSectorSize(), buffer.data()) < 0) return -1; pfs_decrypt(m_cryptops, m_iF00D, &work_ctx); @@ -181,16 +129,35 @@ int PfsFile::decrypt_icv_file(boost::filesystem::path destination_root) const } else { - outputStream.write((char*)buffer.data(), realfileSize); + if (real_bytes_left == 0) + { + m_output << "Encrypted file is larger than expected" << std::endl; + return -1; + } + else if (read_size <= real_bytes_left) + { + outputStream.write((char*)buffer.data(), read_size); + real_bytes_left -= read_size; + } + else + { + outputStream.write((char*)buffer.data(), real_bytes_left); + real_bytes_left = 0; + } } + + bytes_left = bytes_left - read_size; + sector_base = sector_base + num_sectors; } - else + + if (bytes_left != 0) + { + m_output << "Wrong number of bytes left: " << bytes_left << std::endl; + return -1; + } + else if (real_bytes_left != 0) { - //I do not think that icv file supports more than one signature page - //meaning that size is limited to 23 sectors - //lets keep things simple for now - //if it supports more than one signature page - different places in the code will have to be fixed - m_output << "Maximum number of hashes in icv file is exceeded" << std::endl; + m_output << "Wrong number of real bytes left: " << real_bytes_left << std::endl; return -1; } @@ -224,8 +191,6 @@ int PfsFile::decrypt_unicv_file(boost::filesystem::path destination_root) const //in unicv files - there is one hash per sector //that is why we can use get_numSectors() method here - //this is different from icv where it has more hashes than sectors due to merkle tree - //we have to use get_numHashes() there //if number of sectors is less than or same to number that fits into single signature page if(m_table->get_header()->get_numSectors() <= m_table->get_header()->get_binTreeNumMaxAvail()) @@ -264,7 +229,7 @@ int PfsFile::decrypt_unicv_file(boost::filesystem::path destination_root) const for(auto& b : m_table->m_blocks) { //if number of sectors is less than number that fits into single signature page - if(b.get_header()->get_nSignatures() < m_table->get_header()->get_binTreeNumMaxAvail()) + if(b->get_header()->get_nSignatures() < m_table->get_header()->get_binTreeNumMaxAvail()) { std::uint32_t full_block_size = m_table->get_header()->get_binTreeNumMaxAvail() * m_table->get_header()->get_fileSectorSize(); diff --git a/psvpfsparser/PfsFile.h b/psvpfsparser/PfsFile.h index 7823975..ba3528b 100644 --- a/psvpfsparser/PfsFile.h +++ b/psvpfsparser/PfsFile.h @@ -40,7 +40,7 @@ class PfsFile const sce_ng_pfs_file_t& file, const sce_junction& filepath, const sce_ng_pfs_header_t& ngpfs, std::shared_ptr table); private: - int init_crypt_ctx(CryptEngineWorkCtx* work_ctx, sig_tbl_t& block, std::uint32_t sector_base, std::uint32_t tail_size, unsigned char* source) const; + int init_crypt_ctx(CryptEngineWorkCtx* work_ctx, std::shared_ptr block, std::uint32_t sector_base, std::uint32_t tail_size, unsigned char* source) const; int decrypt_icv_file(boost::filesystem::path destination_root) const; diff --git a/psvpfsparser/PfsPageMapper.cpp b/psvpfsparser/PfsPageMapper.cpp index 4dc9d0f..ef0978f 100644 --- a/psvpfsparser/PfsPageMapper.cpp +++ b/psvpfsparser/PfsPageMapper.cpp @@ -52,184 +52,67 @@ std::shared_ptr PfsPageMapper::brutforce_hashes(const std::unique_ return std::shared_ptr(); } -//this is a tree walker function and it should not be a part of the class -int find_zero_sector_index(std::shared_ptr > node, void* ctx) +// verify one merkle tree +int PfsPageMapper::validate_merkle_tree(const std::shared_ptr ftbl, const std::uint32_t page_idx, const std::uint32_t sig_idx, unsigned char* secret) const { - std::pair* ctx_pair = (std::pair*)ctx; + std::shared_ptr page = std::dynamic_pointer_cast(ftbl->m_blocks.at(page_idx)); - if(node->isLeaf()) + // is a leaf + if (2 * sig_idx + 1 >= page->m_signatures.size()) { - if(node->m_index == 0) - { - ctx_pair->second = ctx_pair->first; //save global counter to result - return -1; - } - else - { - ctx_pair->first++; //increase global counter - return 0; - } - } - else - { - ctx_pair->first++; //increase global counter + std::uint32_t child_page_idx = page->get_child_page_idx_for_sig_idx(sig_idx); + // is a page of height > 0 + if (child_page_idx != 0xFFFFFFFF) + return validate_merkle_tree(ftbl, child_page_idx, 0, secret); return 0; } -} -//this is a tree walker function and it should not be a part of the class -int assign_hash(std::shared_ptr > node, void* ctx) -{ - if(!node->isLeaf()) - return 0; - - std::map* sectorHashMap = (std::map*)ctx; - - auto item = sectorHashMap->find(node->m_index); - if(item == sectorHashMap->end()) - throw std::runtime_error("Missing sector hash"); - - node->m_context.m_data.assign(item->second.m_data.begin(), item->second.m_data.end()); - - return 0; -} - -//this is a tree walker function and it should not be a part of the class -int combine_hash(std::shared_ptr > result, std::shared_ptr > left, std::shared_ptr > right, void* ctx) -{ - unsigned char bytes28[0x28] = {0}; - memcpy(bytes28, left->m_context.m_data.data(), 0x14); - memcpy(bytes28 + 0x14, right->m_context.m_data.data(), 0x14); - - std::pair, unsigned char*>* ctx_cast = (std::pair, unsigned char*>*)ctx; - - std::shared_ptr cryptops = ctx_cast->first; - unsigned char* secret = ctx_cast->second; - - result->m_context.m_data.resize(0x14); - cryptops->hmac_sha1(bytes28, result->m_context.m_data.data(), 0x28, secret, 0x14); - - return 0; -} - -//this is a tree walker function and it should not be a part of the class -int collect_hash(std::shared_ptr > node, void* ctx) -{ - std::vector* hashTable = (std::vector*)ctx; - hashTable->push_back(node->m_context); - return 0; -} - -int PfsPageMapper::compare_hash_tables(const std::vector& left, const std::vector& right) -{ - if(left.size() != right.size()) - return -1; - - for(std::size_t i = 0; i < left.size(); i++) + // not a leaf + else { - if(memcmp(left[i].m_data.data(), right[i].m_data.data(), 0x14) != 0) + int ret; + std::uint32_t left_child_idx = 2 * sig_idx + 1; + std::uint32_t right_child_idx = left_child_idx + 1; + + // validate left and right childs + ret = validate_merkle_tree(ftbl, page_idx, left_child_idx, secret); + if (ret < 0) + return ret; + ret = validate_merkle_tree(ftbl, page_idx, right_child_idx, secret); + if (ret < 0) + return ret; + + // validate combined digest + unsigned char result[0x14]; + unsigned char combined[0x28]; + memcpy(combined, page->m_signatures.at(left_child_idx)->m_data.data(), 0x14); + memcpy(combined + 0x14, page->m_signatures.at(right_child_idx)->m_data.data(), 0x14); + m_cryptops->hmac_sha1(combined, result, 0x28, secret, 0x14); + + if (memcmp(result, page->m_signatures.at(sig_idx)->m_data.data(), 0x14) != 0) return -1; + return 0; } - - return 0; } -//pageMap - relates icv salt (icv filename) to junction (real file in filesystem) -//merkleTrees - relates icv table entry (icv file) to merkle tree of real file -//idea is to find icv table entry by icv filename - this way we can relate junction to merkle tree -//then we can read the file and hash it into merkle tree -//then merkle tree is collected into hash table -//then hash table is compared to the hash table from icv table entry -int PfsPageMapper::validate_merkle_trees(const std::unique_ptr& filesDbParser, std::vector, std::shared_ptr > > >& merkleTrees) +// verify the merkle trees top down from the root page +int PfsPageMapper::validate_merkle_trees(const std::unique_ptr& idb, const std::uint32_t files_salt, const std::uint16_t img_spec) const { - const sce_ng_pfs_header_t& ngpfs = filesDbParser->get_header(); - m_output << "Validating merkle trees..." << std::endl; - for(auto entry : merkleTrees) + for (auto ftbl : idb->m_tables) { - //get table - std::shared_ptr table = entry.first; + // skip null tables + if (ftbl->get_header()->get_numSectors() == 0) + continue; - //calculate secret unsigned char secret[0x14]; - scePfsUtilGetSecret(m_cryptops, m_iF00D, secret, m_klicensee, ngpfs.files_salt, img_spec_to_crypto_engine_flag(ngpfs.image_spec), table->get_icv_salt(), 0); + scePfsUtilGetSecret(m_cryptops, m_iF00D, secret, m_klicensee, files_salt, img_spec_to_crypto_engine_flag(img_spec), ftbl->get_icv_salt(), 0); - //find junction - auto junctionIt = m_pageMap.find(table->get_icv_salt()); - if(junctionIt == m_pageMap.end()) - { - m_output << "Table item not found in page map" << std::endl; - return -1; - } - - const sce_junction& junction = junctionIt->second; - - //read junction into sector map - std::ifstream inputStream; - junction.open(inputStream); - - std::uint32_t sectorSize = table->get_header()->get_fileSectorSize(); - std::uintmax_t fileSize = junction.file_size(); - - std::uint32_t nSectors = static_cast(fileSize / sectorSize); - std::uint32_t tailSize = fileSize % sectorSize; - - std::map sectorHashMap; - - std::vector raw_data(sectorSize); - for(std::uint32_t i = 0; i < nSectors; i++) - { - auto currentItem = sectorHashMap.insert(std::make_pair(i, icv())); - icv& currentIcv = currentItem.first->second; - - inputStream.read((char*)raw_data.data(), sectorSize); - - currentIcv.m_data.resize(0x14); - m_cryptops->hmac_sha1(raw_data.data(), currentIcv.m_data.data(), sectorSize, secret, 0x14); - } - - if(tailSize > 0) - { - auto currentItem = sectorHashMap.insert(std::make_pair(nSectors, icv())); - icv& currentIcv = currentItem.first->second; - - inputStream.read((char*)raw_data.data(), tailSize); - - currentIcv.m_data.resize(0x14); - m_cryptops->hmac_sha1(raw_data.data(), currentIcv.m_data.data(), tailSize, secret, 0x14); - } - - try - { - //get merkle tree (it should already be indexed) - std::shared_ptr > mkt = entry.second; - - //assign hashes to leaves - walk_tree(mkt, assign_hash, §orHashMap); - - //calculate node hashes - auto combine_ctx = std::make_pair(m_cryptops, secret); - bottom_top_walk_combine(mkt, combine_hash, &combine_ctx); - - //collect hashes into table - std::vector hashTable; - walk_tree(mkt, collect_hash, &hashTable); - - //compare tables - if(compare_hash_tables(hashTable, table->m_blocks.front().m_signatures) < 0) - { - m_output << "Merkle tree is invalid in file " << junction << std::endl; - return -1; - } - - m_output << "File: " << std::hex << table->get_icv_salt() << " [OK]" << std::endl; - } - catch(std::runtime_error& e) - { - m_output << e.what() << std::endl; - return -1; - } + std::uint32_t page_idx = std::dynamic_pointer_cast(ftbl->get_header())->get_root_page_idx(); + int ret = validate_merkle_tree(ftbl, page_idx, 0, secret); + if (ret < 0) + return ret; } return 0; @@ -311,8 +194,6 @@ int PfsPageMapper::bruteforce_map(const std::unique_ptr& filesDbP } } - std::vector, std::shared_ptr > > > merkleTrees; - //brutforce each sce_iftbl_t record for(auto& t : unicv->m_tables) { @@ -325,41 +206,17 @@ int PfsPageMapper::bruteforce_map(const std::unique_ptr& filesDbP std::shared_ptr found_path; - if(img_spec_to_is_unicv(ngpfs.image_spec)) - { - //in unicv - hash table has same order as sectors in a file - const unsigned char* zeroSectorIcv = t->m_blocks.front().m_signatures.front().m_data.data(); + const unsigned char* zeroSectorIcv = std::dynamic_pointer_cast(t)->get_icv_for_sector(0)->m_data.data(); + try + { //try to find match by hash of zero sector - found_path = brutforce_hashes(filesDbParser, fileDatas, secret, zeroSectorIcv); + found_path = brutforce_hashes(filesDbParser, fileDatas, secret, zeroSectorIcv); } - else + catch(std::runtime_error& e) { - try - { - //create merkle tree for corresponding table - std::shared_ptr > mkt = generate_merkle_tree(t->get_header()->get_numSectors()); - index_merkle_tree(mkt); - - //save merkle tree - merkleTrees.push_back(std::make_pair(t, mkt)); - - //use merkle tree to find index of zero sector in hash table - std::pair ctx; - walk_tree(mkt, find_zero_sector_index, &ctx); - - //in icv - hash table is ordered according to merkle tree structure - //that is why it is required to walk through the tree to find zero sector hash in hash table - const unsigned char* zeroSectorIcv = t->m_blocks.front().m_signatures.at(ctx.second).m_data.data(); - - //try to find match by hash of zero sector - found_path = brutforce_hashes(filesDbParser, fileDatas, secret, zeroSectorIcv); - } - catch(std::runtime_error& e) - { - m_output << e.what() << std::endl; - return -1; - } + m_output << e.what() << std::endl; + return -1; } if(found_path) @@ -378,8 +235,11 @@ int PfsPageMapper::bruteforce_map(const std::unique_ptr& filesDbP //in icv - additional step checks that hash table corresponds to merkle tree if(!img_spec_to_is_unicv(ngpfs.image_spec)) { - if(validate_merkle_trees(filesDbParser, merkleTrees) < 0) + if(validate_merkle_trees(unicv, ngpfs.files_salt, ngpfs.image_spec) < 0) + { + m_output << "Merkle tree is invalid." << std::endl; return -1; + } } if(files.size() != (m_pageMap.size() + m_emptyFiles.size())) diff --git a/psvpfsparser/PfsPageMapper.h b/psvpfsparser/PfsPageMapper.h index 2a0956e..708b6f1 100644 --- a/psvpfsparser/PfsPageMapper.h +++ b/psvpfsparser/PfsPageMapper.h @@ -13,7 +13,7 @@ #include "ICryptoOperations.h" #include "Utils.h" -#include "MerkleTree.hpp" +#include "UnicvDbTypes.h" class FilesDbParser; class UnicvDbParser; @@ -40,9 +40,10 @@ class PfsPageMapper private: std::shared_ptr brutforce_hashes(const std::unique_ptr& filesDbParser, std::map>& fileDatas, const unsigned char* secret, const unsigned char* signature) const; - int compare_hash_tables(const std::vector& left, const std::vector& right); + int compare_hash_tables(const std::vector& left, const std::vector >& right); - int validate_merkle_trees(const std::unique_ptr& filesDbParser, std::vector, std::shared_ptr > > >& merkleTrees); + int validate_merkle_trees(const std::unique_ptr& idb, const std::uint32_t files_salt, const std::uint16_t img_spec) const; + int validate_merkle_tree(const std::shared_ptr ftbl, const std::uint32_t page_idx, const std::uint32_t sig_idx, unsigned char* secret) const; public: int bruteforce_map(const std::unique_ptr& filesDbParser, const std::unique_ptr& unicvDbParser); diff --git a/psvpfsparser/UnicvDbTypes.cpp b/psvpfsparser/UnicvDbTypes.cpp index 8cbfb22..f3a41ea 100644 --- a/psvpfsparser/UnicvDbTypes.cpp +++ b/psvpfsparser/UnicvDbTypes.cpp @@ -88,6 +88,23 @@ bool sig_tbl_header_base_t::validate(std::shared_ptr fft, std: return false; } + return true; +} + +bool sig_tbl_header_base_t::read(std::ifstream& inputStream, std::shared_ptr fft, std::uint32_t sizeCheck) +{ + //read header + inputStream.read((char*)&m_header, sizeof(sig_tbl_header_t)); + + //validate header + return validate(fft, sizeCheck); +} + +bool sig_tbl_header_normal_t::validate(std::shared_ptr fft, std::uint32_t sizeCheck) const +{ + if (!sig_tbl_header_base_t::validate(fft, sizeCheck)) + return false; + //this check is usefull for validating file structure if(m_header.nSignatures != sizeCheck) { @@ -98,24 +115,71 @@ bool sig_tbl_header_base_t::validate(std::shared_ptr fft, std: return true; } -bool sig_tbl_header_base_t::read(std::ifstream& inputStream, std::shared_ptr fft, std::uint32_t sizeCheck, std::vector& signatures) +bool sig_tbl_header_normal_t::validate_tail(std::shared_ptr fft, const std::vector& data) const { - //read header - inputStream.read((char*)&m_header, sizeof(sig_tbl_header_t)); - - //validate header - if(!validate(fft, sizeCheck)) + //validate tail data + if(!isZeroVector(data)) + { + m_output << "Unexpected data instead of padding" << std::endl; + return false; + } + + return true; +} + +bool sig_tbl_header_merkle_t::read(std::ifstream& inputStream, std::shared_ptr fft, std::uint32_t sizeCheck) +{ + // read the page order + inputStream.read((char*)(&m_page_height), sizeof(m_page_height)); + + char padding[12]; + inputStream.read(padding, 12); + if(!isZeroVector(padding, padding + 12)) + { + m_output << "Invalid zero vector" << std::endl; + return false; + } + + return sig_tbl_header_base_t::read(inputStream, fft, sizeCheck); +} + +bool sig_tbl_header_merkle_t::validate(std::shared_ptr fft, std::uint32_t sizeCheck) const +{ + if (!sig_tbl_header_base_t::validate(fft, sizeCheck)) + return false; + + //check signature count + if (m_header.nSignatures > ICV_NUM_ENTRIES) + { + m_output << "Too many signatures in one block: " << m_header.nSignatures << std::endl; + return false; + } + + return true; +} + +bool sig_tbl_base_t::read(std::ifstream& inputStream, std::shared_ptr fft, std::uint32_t sizeCheck) +{ + if (!m_header->read(inputStream, fft, sizeCheck)) return false; //read signatures - for(std::uint32_t c = 0; c < m_header.nSignatures; c++) + for(std::uint32_t c = 0; c < m_header->get_nSignatures(); c++) { - signatures.push_back(icv()); - icv& dte = signatures.back(); - dte.m_data.resize(m_header.sigSize); - inputStream.read((char*)dte.m_data.data(), m_header.sigSize); + m_signatures.push_back(std::make_shared()); + std::shared_ptr dte = m_signatures.back(); + dte->m_data.resize(m_header->get_sigSize()); + inputStream.read((char*)dte->m_data.data(), m_header->get_sigSize()); } + return true; +} + +bool sig_tbl_normal_t::read(std::ifstream& inputStream, std::shared_ptr fft, std::uint32_t sizeCheck) +{ + if (!sig_tbl_base_t::read(inputStream, fft, sizeCheck)) + return false; + //calculate size of tail data - this data should be zero padding //instead of skipping it is validated here that it contains only zeroes std::uint64_t cp = inputStream.tellg(); @@ -130,62 +194,61 @@ bool sig_tbl_header_base_t::read(std::ifstream& inputStream, std::shared_ptrvalidate_tail(fft, data); } +std::shared_ptr sig_tbl_normal_t::get_icv_for_sector(std::uint32_t sector_idx) const +{ + return m_signatures.at(sector_idx); +} -bool sig_tbl_header_normal_t::validate_tail(std::shared_ptr fft, const std::vector& data) const +bool sig_tbl_merkle_t::read(std::ifstream& inputStream, std::shared_ptr fft, std::uint32_t sizeCheck) { - //validate tail data - if(!isZeroVector(data)) - { - m_output << "Unexpected data instead of padding" << std::endl; + if (!sig_tbl_base_t::read(inputStream, fft, sizeCheck)) return false; - } - return true; -} + // seek to the end of the signatures + inputStream.seekg((m_header->get_sigSize()) * (ICV_NUM_ENTRIES - m_header->get_nSignatures()), std::ios::cur); -bool sig_tbl_header_merkle_t::read(std::ifstream& inputStream, std::shared_ptr fft, std::uint32_t sizeCheck, std::vector& signatures) -{ - //read weird 0x10 byte zero header which makes the data not being aligned on page boder - unsigned char zero_header[0x10]; - inputStream.read((char*)zero_header, 0x10); - if(!isZeroVector(zero_header, zero_header + 0x10)) + // read chunk page indices + for (std::uint32_t i = 0; i < m_header->get_nSectors(); i++) { - m_output << "Invalid zero vector" << std::endl; - return false; + std::uint32_t idx; + inputStream.read((char*)(&idx), sizeof(idx)); + if (get_page_height() == 0 && idx != 0xFFFFFFFF) { + std::cout << "Invalid page idx for leaf page: " << idx << std::endl; + return false; + } + + m_child_pages_idx.push_back(idx); } - return sig_tbl_header_base_t::read(inputStream, fft, sizeCheck, signatures); + // seek to the end of the page + inputStream.seekg(4 * (ICV_MAX_SECTORS_PER_PAGE - m_child_pages_idx.size()), std::ios::cur); + + return true; } -bool sig_tbl_header_merkle_t::validate_tail(std::shared_ptr fft, const std::vector& data) const +std::shared_ptr sig_tbl_merkle_t::get_icv_for_sector(std::uint32_t sector_idx) const { - std::vector data_copy(data); - - uint32_t* unk_value_base = (uint32_t*)(data_copy.data() + (data_copy.size() - 0x5C)); + std::uint32_t sig_idx = sector_idx * 2; - //there should be one 0xFFFFFFFF value per sector - for(std::uint32_t i = 0; i < fft->get_header()->get_numSectors(); i++) + while (2 * sig_idx + 1 < m_signatures.size()) { - if(*(unk_value_base + i) != 0xFFFFFFFF) - { - m_output << "Unexpected value in signature table tail" << std::endl; - return false; - } - - *(unk_value_base + i) = 0; //clear that value + sig_idx = 2 * sig_idx + 1; } - //after 0xFFFFFFFF values are cleared - everything should be zeroes (including all other data) - if(!isZeroVector(data_copy)) + return m_signatures.at(sig_idx); +} + +std::uint32_t sig_tbl_merkle_t::get_child_page_idx_for_sig_idx(std::uint32_t sig_idx) const +{ + while (sig_idx % 2 != 0) { - m_output << "Invalid zero vector" << std::endl; - return false; + sig_idx = (sig_idx - 1) / 2; } - - return true; + std::uint32_t child_pages_idx_idx = sig_idx / 2; + return m_child_pages_idx[child_pages_idx_idx]; } //=========== @@ -289,12 +352,6 @@ bool sce_icvdb_header_proxy_t::validate() const return false; } - if(m_header.padding != 0) - { - m_output << "Unexpected padding" << std::endl; - return false; - } - return true; } @@ -353,12 +410,12 @@ bool sce_inull_header_proxy_t::read(std::ifstream& inputStream) //=========== -std::shared_ptr magic_to_sig_tbl(std::string type, std::ostream& output) +std::shared_ptr magic_to_sig_tbl(std::string type, std::ostream& output) { if(type == FT_MAGIC_WORD) - return std::make_shared(output); + return std::make_shared(std::make_shared(output)); else if(type == CV_DB_MAGIC_WORD) - return std::make_shared(output); + return std::make_shared(std::make_shared(output)); else if(type == NULL_MAGIC_WORD) throw std::runtime_error("wrong magic"); else @@ -407,11 +464,11 @@ bool sce_iftbl_base_t::read(std::ifstream& inputStream, std::uint64_t& index, st bool sce_iftbl_base_t::read_block(std::ifstream& inputStream, std::uint64_t& index, std::uint32_t sizeCheck) { //create new signature block - m_blocks.push_back(sig_tbl_t(magic_to_sig_tbl(m_header->get_magic(), m_output))); - sig_tbl_t& fdt = m_blocks.back(); + m_blocks.push_back(magic_to_sig_tbl(m_header->get_magic(), m_output)); + std::shared_ptr fdt = m_blocks.back(); //read and valiate signature block - if(!fdt.read(inputStream, shared_from_this(), sizeCheck)) + if(!fdt->read(inputStream, shared_from_this(), sizeCheck)) return false; index++; @@ -448,6 +505,20 @@ bool sce_iftbl_cvdb_proxy_t::read(std::ifstream& inputStream, std::uint64_t& ind m_page = off2page(currentBlockPos, m_header->get_pageSize()); + return true; +} + + +std::uint32_t sce_iftbl_proxy_t::get_icv_salt() const +{ + return m_page; // unicv.db uses page number as salt +} + +bool sce_iftbl_proxy_t::read(std::ifstream& inputStream, std::uint64_t& index, std::uint32_t icv_salt) +{ + if(!sce_iftbl_cvdb_proxy_t::read(inputStream, index, icv_salt)) + return false; + //check if there are any data blocks after current entry if(m_header->get_numSectors() == 0) return true; @@ -481,13 +552,12 @@ bool sce_iftbl_cvdb_proxy_t::read(std::ifstream& inputStream, std::uint64_t& ind } } - -std::uint32_t sce_iftbl_proxy_t::get_icv_salt() const +std::shared_ptr sce_iftbl_proxy_t::get_icv_for_sector(std::uint32_t sector_idx) const { - return m_page; // unicv.db uses page number as salt + std::uint32_t page_idx = sector_idx / FTBL_MAX_SECTORS_PER_PAGE; + return m_blocks.at(page_idx)->get_icv_for_sector(sector_idx % FTBL_MAX_SECTORS_PER_PAGE); } - std::uint32_t sce_icvdb_proxy_t::get_icv_salt() const { return m_icv_salt; // icv.db uses file name as salt @@ -496,9 +566,33 @@ std::uint32_t sce_icvdb_proxy_t::get_icv_salt() const bool sce_icvdb_proxy_t::read(std::ifstream& inputStream, std::uint64_t& index, std::uint32_t icv_salt) { m_icv_salt = icv_salt; - return sce_iftbl_cvdb_proxy_t::read(inputStream, index, icv_salt); + if(!sce_iftbl_cvdb_proxy_t::read(inputStream, index, icv_salt)) + return false; + + for (std::uint32_t i = 0; i < m_header->get_numPages(); i++) { + if(!read_block(inputStream, index, 0)) + return false; + } + + return m_header->post_validate(m_blocks); } +std::shared_ptr sce_icvdb_proxy_t::get_icv_for_sector(std::uint32_t sector_idx) const +{ + std::uint32_t page_idx = 0; + auto page = std::dynamic_pointer_cast(m_blocks.at(page_idx)); + while (sector_idx > ICV_MAX_SECTORS_PER_PAGE || page->get_page_height() > 0) + { + if (page->get_page_height() == 0) + { + sector_idx -= ICV_MAX_SECTORS_PER_PAGE; + } + page_idx++; + page = std::dynamic_pointer_cast(m_blocks.at(page_idx)); + } + + return m_blocks.at(page_idx)->get_icv_for_sector(sector_idx); +} std::uint32_t sce_inull_proxy_t::get_icv_salt() const { diff --git a/psvpfsparser/UnicvDbTypes.h b/psvpfsparser/UnicvDbTypes.h index 67af12f..0299127 100644 --- a/psvpfsparser/UnicvDbTypes.h +++ b/psvpfsparser/UnicvDbTypes.h @@ -33,6 +33,8 @@ #define NULL_EXPECTED_VERSION 1 #define ICV_NUM_ENTRIES 0x2D +#define ICV_MAX_SECTORS_PER_PAGE ((ICV_NUM_ENTRIES + 1) / 2) +#define FTBL_MAX_SECTORS_PER_PAGE 0x32 #pragma pack(push, 1) @@ -99,9 +101,9 @@ struct sce_iftbl_header_t std::uint8_t magic[8]; //SCEIFTBL std::uint32_t version; // this is probably version? value is always 2 std::uint32_t pageSize; //expected 0x400 - std::uint32_t binTreeNumMaxAvail; // this is probably max number of sectors in a single sig_tbl_t. expected value is 0x32 - std::uint32_t nSectors; //this shows how many sectors of data in total will follow this block. one sig_tbl_t can contain 0x32 sectors at max - //multiple sig_tbl_t group into single file + std::uint32_t binTreeNumMaxAvail; // this is probably max number of sectors in a single sig_tbl_normal_t. expected value is 0x32 + std::uint32_t nSectors; //this shows how many sectors of data in total will follow this block. one sig_tbl_normal_t can contain 0x32 sectors at max + //multiple sig_tbl_normal_t group into single file //This is sector size for files.db std::uint32_t fileSectorSize; // expected 0x8000 @@ -119,10 +121,10 @@ struct sce_icvdb_header_t std::uint32_t version; // this is probably version? value is always 2 std::uint32_t fileSectorSize; std::uint32_t pageSize; //expected 0x400 - std::uint32_t padding; + std::uint32_t root_page_idx; // index of the root page std::uint32_t unk0; //0xFFFFFFFF std::uint32_t unk1; //0xFFFFFFFF - std::uint64_t dataSize; // from next chunk maybe? or block size + std::uint64_t dataSize; // total size of all data pages; a multiple of pageSize std::uint32_t nSectors; std::uint8_t merkleTreeRoot[20]; }; @@ -197,10 +199,12 @@ class sig_tbl_header_base_t return m_header.padding; } + virtual std::uint32_t get_nSectors() const = 0; + public: - bool validate(std::shared_ptr fft, std::uint32_t sizeCheck) const; + virtual bool validate(std::shared_ptr fft, std::uint32_t sizeCheck) const; - virtual bool read(std::ifstream& inputStream, std::shared_ptr fft, std::uint32_t sizeCheck, std::vector& signatures); + virtual bool read(std::ifstream& inputStream, std::shared_ptr fft, std::uint32_t sizeCheck); virtual bool validate_tail(std::shared_ptr fft, const std::vector& data) const = 0; }; @@ -213,22 +217,69 @@ class sig_tbl_header_normal_t : public sig_tbl_header_base_t { } + std::uint32_t get_nSectors() const override + { + return get_nSignatures(); + } + public: + bool validate(std::shared_ptr fft, std::uint32_t sizeCheck) const override; bool validate_tail(std::shared_ptr fft, const std::vector& data) const override; }; class sig_tbl_header_merkle_t : public sig_tbl_header_base_t { +private: + std::uint32_t m_page_height; // height from leaf pages + public: sig_tbl_header_merkle_t(std::ostream& output) - : sig_tbl_header_base_t(output) + : sig_tbl_header_base_t(output), m_page_height(-1) + { + } + + std::uint32_t get_nSectors() const override { + return (get_nSignatures() + 1) / 2; } public: - bool read(std::ifstream& inputStream, std::shared_ptr fft, std::uint32_t sizeCheck, std::vector& signatures) override; + bool read(std::ifstream& inputStream, std::shared_ptr fft, std::uint32_t sizeCheck) override; - bool validate_tail(std::shared_ptr fft, const std::vector& data) const override; + bool validate(std::shared_ptr fft, std::uint32_t sizeCheck) const override; + + bool validate_tail(std::shared_ptr fft, const std::vector& data) const override + { + return true; + } + + std::uint32_t get_page_height() const + { + return m_page_height; + } +}; + +class sig_tbl_base_t +{ +protected: + std::shared_ptr m_header; + +public: + sig_tbl_base_t(std::shared_ptr header) + : m_header(header) {} + + std::vector > m_signatures; + + std::shared_ptr get_header() const + { + return m_header; + } + + virtual bool read(std::ifstream& inputStream, std::shared_ptr fft, std::uint32_t sizeCheck); + virtual std::shared_ptr get_icv_for_sector(std::uint32_t sector_idx) const + { + throw std::runtime_error("Unimplemented"); + } }; //this is a signature table structure - it contains header and list of signatures @@ -236,29 +287,37 @@ class sig_tbl_header_merkle_t : public sig_tbl_header_base_t //signature table that is used to verify file hashes //it can hold 0x32 signatures at max //each signature corresponds to block in a real file. block should have size fileSectorSize (0x8000) -class sig_tbl_t +class sig_tbl_normal_t : public sig_tbl_base_t +{ +public: + sig_tbl_normal_t(std::shared_ptr header) + : sig_tbl_base_t(header) {} + +public: + bool read(std::ifstream& inputStream, std::shared_ptr fft, std::uint32_t sizeCheck) override; + std::shared_ptr get_icv_for_sector(std::uint32_t sector_idx) const override; +}; + +class sig_tbl_merkle_t : public sig_tbl_base_t { private: - std::shared_ptr m_header; + std::vector m_child_pages_idx; // merkel tree child page idx for pages of height > 0 public: - sig_tbl_t(std::shared_ptr header) - : m_header(header) + sig_tbl_merkle_t(std::shared_ptr header) + : sig_tbl_base_t(header) { } -public: - std::vector m_signatures; + bool read(std::ifstream& inputStream, std::shared_ptr fft, std::uint32_t sizeCheck) override; + std::shared_ptr get_icv_for_sector(std::uint32_t sector_idx) const override; - std::shared_ptr get_header() const + bool get_page_height() const { - return m_header; + return std::dynamic_pointer_cast(m_header)->get_page_height(); } - bool read(std::ifstream& inputStream, std::shared_ptr fft, std::uint32_t sizeCheck) - { - return m_header->read(inputStream, fft, sizeCheck, m_signatures); - } + std::uint32_t get_child_page_idx_for_sig_idx(std::uint32_t sig_idx) const; }; //================================================= @@ -287,12 +346,14 @@ class sce_iftbl_header_base_t virtual std::string get_magic() const = 0; + virtual std::uint32_t get_numPages() const = 0; + public: virtual bool validate() const = 0; virtual bool read(std::ifstream& inputStream) = 0; - virtual bool post_validate(const std::vector& blocks) const = 0; + virtual bool post_validate(const std::vector >& blocks) const = 0; }; class sce_iftbl_header_proxy_t : public sce_iftbl_header_base_t @@ -350,12 +411,18 @@ class sce_iftbl_header_proxy_t : public sce_iftbl_header_base_t return std::string((char*)m_header.magic, 8); } + std::uint32_t get_numPages() const override + { + return (get_numHashes() / get_binTreeNumMaxAvail()) + + (get_numHashes() % get_binTreeNumMaxAvail() == 0 ? 0 : 1); + } + public: bool validate() const override; bool read(std::ifstream& inputStream) override; - bool post_validate(const std::vector& blocks) const override + bool post_validate(const std::vector >& blocks) const override { return true; } @@ -419,14 +486,24 @@ class sce_icvdb_header_proxy_t : public sce_iftbl_header_base_t return std::string((char*)m_header.magic, 8); } + std::uint32_t get_numPages() const override + { + return m_header.dataSize / m_header.pageSize; + } + + std::uint32_t get_root_page_idx() const + { + return m_header.root_page_idx; + } + public: bool validate() const override; bool read(std::ifstream& inputStream) override; - bool post_validate(const std::vector& blocks) const override + bool post_validate(const std::vector >& blocks) const override { - const unsigned char* rootSig = blocks.front().m_signatures.front().m_data.data(); + const unsigned char* rootSig = blocks[get_root_page_idx()]->m_signatures.front()->m_data.data(); if(memcmp(rootSig, m_header.merkleTreeRoot, 0x14) != 0) { m_output << "Root icv is invalid" << std::endl; @@ -491,12 +568,17 @@ class sce_inull_header_proxy_t : public sce_iftbl_header_base_t return std::string((char*)m_header.magic, 8); } + std::uint32_t get_numPages() const override + { + return 0; + } + public: bool validate() const override; bool read(std::ifstream& inputStream) override; - bool post_validate(const std::vector& blocks) const override + bool post_validate(const std::vector >& blocks) const override { return true; } @@ -514,7 +596,7 @@ class sce_iftbl_base_t : public std::enable_shared_from_this std::uint32_t m_page; public: - std::vector m_blocks; + std::vector > m_blocks; protected: std::ostream& m_output; @@ -557,10 +639,9 @@ class sce_iftbl_cvdb_proxy_t : public sce_iftbl_base_t public: bool read(std::ifstream& inputStream, std::uint64_t& index, std::uint32_t icv_salt) override; + virtual std::shared_ptr get_icv_for_sector(std::uint32_t sector_idx) const = 0; }; -//for now these types do not implement any additional logic that is different from base classes -//however clear separation in 3 different types is essential class sce_iftbl_proxy_t : public sce_iftbl_cvdb_proxy_t { public: @@ -571,6 +652,8 @@ class sce_iftbl_proxy_t : public sce_iftbl_cvdb_proxy_t public: std::uint32_t get_icv_salt() const override; + bool read(std::ifstream& inputStream, std::uint64_t& index, std::uint32_t icv_salt) override; + std::shared_ptr get_icv_for_sector(std::uint32_t sector_idx) const override; }; class sce_icvdb_proxy_t : public sce_iftbl_cvdb_proxy_t @@ -589,6 +672,7 @@ class sce_icvdb_proxy_t : public sce_iftbl_cvdb_proxy_t public: bool read(std::ifstream& inputStream, std::uint64_t& index, std::uint32_t icv_salt) override; + std::shared_ptr get_icv_for_sector(std::uint32_t sector_idx) const override; }; class sce_inull_proxy_t : public sce_iftbl_base_t @@ -668,7 +752,7 @@ class sce_icvdb_t : public sce_idb_base_t #pragma pack(pop) -std::shared_ptr magic_to_sig_tbl(std::string type, std::ostream& output); +std::shared_ptr magic_to_sig_tbl(std::string type, std::ostream& output); std::shared_ptr magic_to_ftbl_header(std::string type, std::ostream& output);