From fe9892932f197e80810ed98b0301687c968f9074 Mon Sep 17 00:00:00 2001 From: Zangetsu38 Date: Mon, 28 Oct 2024 08:03:55 +0100 Subject: [PATCH] package: Add support installation of Preinstalled firmware. - Add support of exfat partition for installing it. --- vita3k/packages/CMakeLists.txt | 2 + vita3k/packages/include/packages/exfat.h | 113 ++++++++++++ vita3k/packages/include/packages/sce_types.h | 1 + vita3k/packages/src/exfat.cpp | 171 +++++++++++++++++++ vita3k/packages/src/pup.cpp | 3 + vita3k/packages/src/sce_utils.cpp | 13 ++ 6 files changed, 303 insertions(+) create mode 100644 vita3k/packages/include/packages/exfat.h create mode 100644 vita3k/packages/src/exfat.cpp diff --git a/vita3k/packages/CMakeLists.txt b/vita3k/packages/CMakeLists.txt index e93d6e6786..b6b126e9fb 100644 --- a/vita3k/packages/CMakeLists.txt +++ b/vita3k/packages/CMakeLists.txt @@ -1,9 +1,11 @@ add_library(packages STATIC + src/exfat.cpp src/license.cpp src/pkg.cpp src/pup.cpp src/sce_utils.cpp src/sfo.cpp + include/packages/exfat.h include/packages/functions.h include/packages/pkg.h include/packages/sce_types.h diff --git a/vita3k/packages/include/packages/exfat.h b/vita3k/packages/include/packages/exfat.h new file mode 100644 index 0000000000..bdea484dde --- /dev/null +++ b/vita3k/packages/include/packages/exfat.h @@ -0,0 +1,113 @@ +// Vita3K emulator project +// Copyright (C) 2024 Vita3K team +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that 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, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +#pragma once + +#include + +// Credits to relan for their original work on this https://github.com/relan/exfat + +#define EXFAT_ENAME_MAX 15 + +struct ExFATSuperBlock { + uint8_t jump[3]; /* 0x00 jmp and nop instructions */ + uint8_t oem_name[8]; /* 0x03 "EXFAT " */ + uint8_t __unused1[53]; /* 0x0B always 0 */ + uint64_t sector_start; /* 0x40 partition first sector */ + uint64_t sector_count; /* 0x48 partition sectors count */ + uint32_t fat_sector_start; /* 0x50 FAT first sector */ + uint32_t fat_sector_count; /* 0x54 FAT sectors count */ + uint32_t cluster_sector_start; /* 0x58 first cluster sector */ + uint32_t cluster_count; /* 0x5C total clusters count */ + uint32_t rootdir_cluster; /* 0x60 first cluster of the root dir */ + uint32_t volume_serial; /* 0x64 volume serial number */ + + struct { /* 0x68 FS version */ + uint8_t minor; /* Minor version */ + uint8_t major; /* Major version */ + } version; + + uint16_t volume_state; /* 0x6A volume state flags */ + uint8_t sector_bits; /* 0x6C sector size as (1 << n) */ + uint8_t spc_bits; /* 0x6D sectors per cluster as (1 << n) */ + uint8_t fat_count; /* 0x6E always 1 */ + uint8_t drive_no; /* 0x6F always 0x80 */ + uint8_t allocated_percent; /* 0x70 percentage of allocated space */ + uint8_t __unused2[397]; /* 0x71 always 0 */ + uint16_t boot_signature; /* 0xAA55 */ +}; + +struct ExFATFileEntry { + uint8_t type; /* EXFAT_ENTRY_FILE */ + uint8_t continuations; + uint16_t checksum; + uint16_t attrib; /* combination of EXFAT_ATTRIB_xxx */ + uint16_t __unknown1; + uint16_t crtime, crdate; /* creation date and time */ + uint16_t mtime, mdate; /* latest modification date and time */ + uint16_t atime, adate; /* latest access date and time */ + uint8_t crtime_cs; /* creation time in cs (centiseconds) */ + uint8_t mtime_cs; /* latest modification time in cs */ + uint8_t crtime_tzo, mtime_tzo, atime_tzo; /* timezone offset encoded */ + uint8_t __unknown2[7]; +}; + +struct ExFATFileEntryInfo { + uint8_t type; /* EXFAT_ENTRY_FILE_INFO */ + uint8_t flags; /* combination of EXFAT_FLAG_xxx */ + uint8_t __unknown1; + uint8_t name_length; + uint16_t name_hash; + uint16_t __unknown2; + uint64_t valid_size; /* in bytes, less or equal to size */ + uint8_t __unknown3[4]; + uint32_t start_cluster; + uint64_t size; /* in bytes */ +}; + +struct ExFATEntryName { + uint8_t type; /* EXFAT_ENTRY_FILE_NAME */ + uint8_t __unknown; + uint16_t name[EXFAT_ENAME_MAX]; /* in UTF-16LE */ +}; + +enum ExFATAttrib { + EXFAT_ATTRIB_RO = 0x01, + EXFAT_ATTRIB_HIDDEN = 0x02, + EXFAT_ATTRIB_SYSTEM = 0x04, + EXFAT_ATTRIB_VOLUME = 0x08, + EXFAT_ATTRIB_DIR = 0x10, + EXFAT_ATTRIB_ARCH = 0x20 +}; + +enum ExFATEntryType { + EXFAT_ENTRY_VALID = 0x80, + EXFAT_ENTRY_CONTINUED = 0x40, + EXFAT_ENTRY_OPTIONAL = 0x20, + + EXFAT_ENTRY_BITMAP = (0x01 | EXFAT_ENTRY_VALID), + EXFAT_ENTRY_UPCASE = (0x02 | EXFAT_ENTRY_VALID), + EXFAT_ENTRY_LABEL = (0x03 | EXFAT_ENTRY_VALID), + EXFAT_ENTRY_FILE = (0x05 | EXFAT_ENTRY_VALID), + EXFAT_ENTRY_FILE_INFO = (0x00 | EXFAT_ENTRY_VALID | EXFAT_ENTRY_CONTINUED), + EXFAT_ENTRY_FILE_NAME = (0x01 | EXFAT_ENTRY_VALID | EXFAT_ENTRY_CONTINUED), + EXFAT_ENTRY_FILE_TAIL = (0x00 | EXFAT_ENTRY_VALID | EXFAT_ENTRY_CONTINUED | EXFAT_ENTRY_OPTIONAL) +}; + +namespace exfat { +void extract_exfat(fs::ifstream &img, const fs::path &output_path); +} // namespace exfat diff --git a/vita3k/packages/include/packages/sce_types.h b/vita3k/packages/include/packages/sce_types.h index ed4632b79e..9041f058c3 100644 --- a/vita3k/packages/include/packages/sce_types.h +++ b/vita3k/packages/include/packages/sce_types.h @@ -651,6 +651,7 @@ class SceRIF { }; void register_keys(KeyStore &SCE_KEYS, int type); +void extract_exfat(const fs::path &partition_path, const std::string &partition, const fs::path &pref_path); void extract_fat(const fs::path &partition_path, const std::string &partition, const fs::path &pref_path); std::string decompress_segments(const std::vector &decrypted_data, const uint64_t &size); std::tuple get_key_type(std::ifstream &file, const SceHeader &sce_hdr); diff --git a/vita3k/packages/src/exfat.cpp b/vita3k/packages/src/exfat.cpp new file mode 100644 index 0000000000..7bf0a78d99 --- /dev/null +++ b/vita3k/packages/src/exfat.cpp @@ -0,0 +1,171 @@ +// Vita3K emulator project +// Copyright (C) 2024 Vita3K team +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that 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, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +#include + +namespace exfat { + +static std::string get_exfat_file_name(fs::ifstream &img, uint8_t continuations) { + std::vector utf16Data; + + // Loop depending on the number of continuations, subtract 1 because continuations start to 2 + for (int cont = 0; cont < (continuations - 1); ++cont) { + // read the name entry + ExFATEntryName nameEntry; + img.read(reinterpret_cast(&nameEntry), sizeof(ExFATEntryName)); + + // Check if the entry is of type `0xC1` + if (nameEntry.type == 0xC1) { + // Adding the name characters to `utf16Data` + for (int i = 0; i < EXFAT_ENAME_MAX; ++i) + utf16Data.push_back(nameEntry.name[i]); + } else { + LOG_ERROR("Error: Unexpected type of continuation entry (expected 0xC1, found: 0x{:X}, on offset: {})", nameEntry.type, static_cast(img.tellg())); + break; + } + } + + // Adding null-terminator + utf16Data.push_back(0); + + // Convert name to UTF-8 + std::string utf8String; + for (uint16_t ch : utf16Data) { + if (ch == 0) { + break; + } + if (ch < 0x80) { + utf8String.push_back(static_cast(ch)); + } else if (ch < 0x800) { + utf8String.push_back(0xC0 | (ch >> 6)); + utf8String.push_back(0x80 | (ch & 0x3F)); + } else { + utf8String.push_back(0xE0 | (ch >> 12)); + utf8String.push_back(0x80 | ((ch >> 6) & 0x3F)); + utf8String.push_back(0x80 | (ch & 0x3F)); + } + } + + return utf8String; +} + +static uint64_t get_cluster_offset(const ExFATSuperBlock &super_block, uint32_t cluster) { + const uint64_t sector_size = static_cast(1 << super_block.sector_bits); + const uint64_t sectors_per_cluster = static_cast(1 << super_block.spc_bits); + const uint64_t cluster_size = sector_size * sectors_per_cluster; + + // The cluster number is 0-based, so we need to add 1 + return static_cast(cluster + 1) * cluster_size; +} + +static void traverse_directory(fs::ifstream &img, const uint64_t img_size, std::vector &offset_stack, const ExFATSuperBlock &super_block, + const uint32_t cluster, const fs::path &output_path, fs::path current_dir) { + // Get the offset of the cluster + const auto cluster_offset = get_cluster_offset(super_block, cluster); + img.seekg(cluster_offset); + + // Loop through the entries in the cluster + while (img.tellg() < img_size) { + // Read the file entry + ExFATFileEntry file_entry; + img.read(reinterpret_cast(&file_entry), sizeof(ExFATFileEntry)); + + switch (file_entry.type) { + case EXFAT_ENTRY_BITMAP: + case EXFAT_ENTRY_UPCASE: + case EXFAT_ENTRY_LABEL: + // Skip these entries + break; + case EXFAT_ENTRY_FILE: { + // Read the file info + ExFATFileEntryInfo file_info; + img.read(reinterpret_cast(&file_info), sizeof(ExFATFileEntryInfo)); + + // Get the name of file or directory + std::string name = get_exfat_file_name(img, file_entry.continuations); + + // Set path of the current file or directory + const auto subdir = current_dir / name; + const auto current_output_path = output_path / subdir; + + // Get the current offset + const uint64_t current_offset = img.tellg(); + + if (file_entry.attrib & EXFAT_ATTRIB_DIR) { + fs::create_directories(current_output_path); + + // Save the current offset before entering + offset_stack.push_back(current_offset); + + // Traverse the directory recursively + traverse_directory(img, img_size, offset_stack, super_block, file_info.start_cluster, output_path, subdir); + } else if (file_entry.attrib & EXFAT_ATTRIB_ARCH) { + // Get offset of current file + const auto file_offset = get_cluster_offset(super_block, file_info.start_cluster); + + // Seek to the file offset + img.seekg(file_offset); + + // Create the file and write the data + fs::ofstream output_file(current_output_path, std::ios::binary); + const auto file_size = file_info.size; + std::vector buffer(file_size); + img.read(buffer.data(), file_size); + output_file.write(buffer.data(), file_size); + output_file.close(); + + // Go back to the current offset for the next entry + img.seekg(current_offset); + } + break; + } + default: + // End of directory, return to parent if entries remain, otherwise seek to file end. + if (!offset_stack.empty()) { + img.seekg(offset_stack.back()); + offset_stack.pop_back(); + current_dir = current_dir.parent_path(); + } else { + img.seekg(img_size); + } + break; + } + } +} + +void extract_exfat(fs::ifstream &img, const fs::path &output_path) { + // Get the size of the image + const auto img_size = img.tellg(); + + // Go back to the beginning + img.seekg(0, std::ios::beg); + + // Read the super block + ExFATSuperBlock super_block; + img.read(reinterpret_cast(&super_block), sizeof(ExFATSuperBlock)); + + // Stack to keep track of the offsets + std::vector offset_stack; + + // Current directory + fs::path current_dir; + + // Traverse the root directory + traverse_directory(img, img_size, offset_stack, super_block, super_block.rootdir_cluster, output_path, current_dir); +} + +} // namespace exfat diff --git a/vita3k/packages/src/pup.cpp b/vita3k/packages/src/pup.cpp index 88efaa42ee..dbce4c352c 100644 --- a/vita3k/packages/src/pup.cpp +++ b/vita3k/packages/src/pup.cpp @@ -242,6 +242,7 @@ static void decrypt_pup_packages(const fs::path &src, const fs::path &dest, KeyS } join_files(dest, "os0-", dest / "os0.img"); + join_files(dest, "pd0-", dest / "pd0.img"); join_files(dest, "vs0-", dest / "vs0.img"); join_files(dest, "sa0-", dest / "sa0.img"); } @@ -282,6 +283,8 @@ void install_pup(const fs::path &pref_path, const fs::path &pup_path, const std: } } } + if (fs::file_size(pup_dec / "pd0.img") > 0) + extract_exfat(pup_dec, "pd0.img", pref_path); if (fs::file_size(pup_dec / "sa0.img") > 0) extract_fat(pup_dec, "sa0.img", pref_path); if (fs::file_size(pup_dec / "vs0.img") > 0) { diff --git a/vita3k/packages/src/sce_utils.cpp b/vita3k/packages/src/sce_utils.cpp index 443e675966..a6eb89598f 100644 --- a/vita3k/packages/src/sce_utils.cpp +++ b/vita3k/packages/src/sce_utils.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -652,6 +653,18 @@ static void traverse_directory(Fat16::Image &img, Fat16::Entry mee, const fs::pa } } +void extract_exfat(const fs::path &partition_path, const std::string &partition, const fs::path &pref_path) { + // Open the partition file for reading in binary mode and seek to the end + fs::ifstream img(partition_path / partition, std::ios::binary | std::ios::ate); + if (!img.is_open()) { + LOG_ERROR("Failed to open partition file"); + return; + } + + exfat::extract_exfat(img, pref_path / partition.substr(0, 3)); + img.close(); +} + void extract_fat(const fs::path &partition_path, const std::string &partition, const fs::path &pref_path) { FILE *f = FOPEN((partition_path / partition).native().c_str(), "rb"); Fat16::Image img(