├── .clang-format ├── LICENSE ├── README.md ├── include ├── common.h ├── crc32.h ├── dump.h ├── elf.h ├── elf_common.h ├── elf_structs.h ├── fself.h ├── gp4.h ├── md5.h ├── npbind.h ├── pfs.h ├── pkg.h ├── pugiconfig.hpp ├── pugixml.hpp ├── sfo.h ├── sha1.h └── sha256.h ├── src ├── crc32.cpp ├── dump.cpp ├── elf.cpp ├── fself.cpp ├── gp4.cpp ├── main.cpp ├── md5.cpp ├── npbind.cpp ├── pfs.cpp ├── pkg.cpp ├── pugixml.cpp ├── sfo.cpp ├── sha1.cpp └── sha256.cpp └── tests ├── dump_test.h ├── elf_test.h ├── files ├── elf │ ├── brokenElfMagic.elf │ ├── brokenElfMagic.self │ ├── brokenElfSize.elf │ ├── brokenElfSize.self │ ├── brokenSceHeader.self │ ├── brokenSceHeaderNpdrm.self │ ├── brokenSelfMagic.self │ ├── brokenSelfSize.self │ ├── getAppVersion_0s.self │ ├── getAppVersion_0s_(NPDRM_Header).self │ ├── getAppVersion_Fs.self │ ├── getAppVersion_Fs_(NPDRM_Header).self │ ├── getDigest_0s.self │ ├── getDigest_0s_(NPDRM_Header).self │ ├── getDigest_Fs.self │ ├── getDigest_Fs_(NPDRM_Header).self │ ├── getFwVersion_0s.self │ ├── getFwVersion_0s_(NPDRM_Header).self │ ├── getFwVersion_Fs.self │ ├── getFwVersion_Fs_(NPDRM_Header).self │ ├── getPaid_0s.self │ ├── getPaid_0s_(NPDRM_Header).self │ ├── getPaid_Fs.self │ ├── getPaid_Fs_(NPDRM_Header).self │ ├── isValidDecrypt_Decrypted_1.elf │ ├── isValidDecrypt_Decrypted_2.elf │ ├── isValidDecrypt_Decrypted_3.elf │ ├── isValidDecrypt_Decrypted_4.elf │ ├── isValidDecrypt_Encrypted_1.self │ ├── isValidDecrypt_Encrypted_1_(NPDRM_Header).self │ ├── isValidDecrypt_Encrypted_2.self │ ├── isValidDecrypt_Encrypted_2_(NPDRM_Header).self │ ├── isValidDecrypt_Encrypted_3.self │ ├── isValidDecrypt_Encrypted_3_(NPDRM_Header).self │ ├── isValidDecrypt_Encrypted_4.self │ ├── isValidDecrypt_Encrypted_4_(NPDRM_Header).self │ ├── noPermission.ext │ ├── notAFile.ext │ │ └── .gitkeep │ ├── ptype_Fake.self │ ├── ptype_HostKernel.self │ ├── ptype_NpdrmDynlib.self │ ├── ptype_NpdrmExec.self │ ├── ptype_SecureKernel.self │ ├── ptype_SecureModule.self │ ├── ptype_SystemDynlib.self │ ├── ptype_SystemExec.self │ ├── ptype_Unknown.self │ ├── sceHeaderOffset_0x410.self │ ├── sceHeaderOffset_0xC0.self │ ├── sectionHeaderZero.elf │ ├── valid.elf │ └── valid.self ├── npbind │ ├── brokenNpbindDigest.dat │ ├── brokenNpbindEntriesSize.dat │ ├── brokenNpbindHeaderSize.dat │ ├── brokenNpbindMagic.dat │ ├── noPermission.ext │ ├── notAFile.ext │ │ └── .gitkeep │ ├── valid_NoEntry.dat │ ├── valid_NoEntry_Alternative.dat │ ├── valid_OneEntry.dat │ ├── valid_ThreeEntries.dat │ └── valid_TwoEntries.dat ├── pkg │ ├── noPermission.ext │ ├── notADirectory.ext │ └── notAFile.ext │ │ └── .gitkeep └── sfo │ ├── noPermission.ext │ └── notAFile.ext │ └── .gitkeep ├── fself_test.h ├── gp4_test.h ├── npbind_test.h ├── pfs_test.h ├── pkg_test.h ├── sfo_test.h └── testing.h /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | # BasedOnStyle: LLVM 4 | AccessModifierOffset: -2 5 | AlignAfterOpenBracket: Align 6 | AlignConsecutiveAssignments: false 7 | AlignConsecutiveDeclarations: false 8 | AlignEscapedNewlines: Right 9 | AlignOperands: true 10 | AlignTrailingComments: true 11 | AllowAllParametersOfDeclarationOnNextLine: true 12 | AllowShortBlocksOnASingleLine: false 13 | AllowShortCaseLabelsOnASingleLine: false 14 | AllowShortFunctionsOnASingleLine: All 15 | AllowShortIfStatementsOnASingleLine: false 16 | AllowShortLoopsOnASingleLine: false 17 | AlwaysBreakAfterDefinitionReturnType: None 18 | AlwaysBreakAfterReturnType: None 19 | AlwaysBreakBeforeMultilineStrings: false 20 | AlwaysBreakTemplateDeclarations: false 21 | BinPackArguments: true 22 | BinPackParameters: true 23 | BraceWrapping: 24 | AfterClass: false 25 | AfterControlStatement: false 26 | AfterEnum: false 27 | AfterFunction: false 28 | AfterNamespace: false 29 | AfterObjCDeclaration: false 30 | AfterStruct: false 31 | AfterUnion: false 32 | AfterExternBlock: false 33 | BeforeCatch: false 34 | BeforeElse: false 35 | IndentBraces: false 36 | SplitEmptyFunction: true 37 | SplitEmptyRecord: true 38 | SplitEmptyNamespace: true 39 | BreakBeforeBinaryOperators: None 40 | BreakBeforeBraces: Attach 41 | BreakBeforeInheritanceComma: false 42 | BreakBeforeTernaryOperators: true 43 | BreakConstructorInitializersBeforeComma: false 44 | BreakConstructorInitializers: BeforeColon 45 | BreakAfterJavaFieldAnnotations: false 46 | BreakStringLiterals: true 47 | ColumnLimit: 0 48 | CommentPragmas: '^ IWYU pragma:' 49 | CompactNamespaces: false 50 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 51 | ConstructorInitializerIndentWidth: 4 52 | ContinuationIndentWidth: 4 53 | Cpp11BracedListStyle: true 54 | DerivePointerAlignment: false 55 | DisableFormat: false 56 | ExperimentalAutoDetectBinPacking: false 57 | FixNamespaceComments: true 58 | ForEachMacros: 59 | - foreach 60 | - Q_FOREACH 61 | - BOOST_FOREACH 62 | IncludeBlocks: Preserve 63 | IncludeCategories: 64 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 65 | Priority: 2 66 | - Regex: '^(<|"(gtest|gmock|isl|json)/)' 67 | Priority: 3 68 | - Regex: '.*' 69 | Priority: 1 70 | IncludeIsMainRegex: '(Test)?$' 71 | IndentCaseLabels: false 72 | IndentPPDirectives: None 73 | IndentWidth: 2 74 | IndentWrappedFunctionNames: false 75 | JavaScriptQuotes: Leave 76 | JavaScriptWrapImports: true 77 | KeepEmptyLinesAtTheStartOfBlocks: true 78 | MacroBlockBegin: '' 79 | MacroBlockEnd: '' 80 | MaxEmptyLinesToKeep: 1 81 | NamespaceIndentation: None 82 | ObjCBlockIndentWidth: 2 83 | ObjCSpaceAfterProperty: false 84 | ObjCSpaceBeforeProtocolList: true 85 | PenaltyBreakAssignment: 2 86 | PenaltyBreakBeforeFirstCallParameter: 19 87 | PenaltyBreakComment: 300 88 | PenaltyBreakFirstLessLess: 120 89 | PenaltyBreakString: 1000 90 | PenaltyExcessCharacter: 1000000 91 | PenaltyReturnTypeOnItsOwnLine: 60 92 | PointerAlignment: Right 93 | RawStringFormats: 94 | - Delimiter: pb 95 | Language: TextProto 96 | BasedOnStyle: google 97 | ReflowComments: true 98 | SortIncludes: true 99 | SortUsingDeclarations: true 100 | SpaceAfterCStyleCast: false 101 | SpaceAfterTemplateKeyword: true 102 | SpaceBeforeAssignmentOperators: true 103 | SpaceBeforeParens: ControlStatements 104 | SpaceInEmptyParentheses: false 105 | SpacesBeforeTrailingComments: 1 106 | SpacesInAngles: false 107 | SpacesInContainerLiterals: true 108 | SpacesInCStyleCastParentheses: false 109 | SpacesInParentheses: false 110 | SpacesInSquareBrackets: false 111 | Standard: Cpp11 112 | TabWidth: 2 113 | UseTab: Never 114 | ... 115 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dumper-testing 2 | 3 | Rewriting PS4 application dumper from scratch in C++. This repo will be deleted when a permanent repo is setup. Don't bother cloning unless you're going to be making immediate changes. 4 | 5 | **No code here is finished or guaranteed to work.** Goal is for readability first to allow easier collaboration until everything is working 100%, don't want to optimize it too early: 6 | 7 | 1. Get it all working. For the first draft we will assume the asset is completely installed and is currently running on the system. 8 | 2. Refactor to make more sense. 9 | 3. More robust. Find/fix edge cases and have good, descriptive, error messages for issues that cannot be handled automatically. 10 | 4. Setup tests so we don't break anything trying to enhance performance. 11 | 5. Performance. 12 | 13 | > "Move fast break things" 14 | 15 | **ANYONE** can contribute. Code is currently licensed under GPLv3, by submitting a pull request you agree to this term and agree to possible relicensing later. 16 | 17 | ## Known Issues 18 | 19 | - sc0 entry decryption (Like `npbind.dat`)* 20 | - "New" binary decryption function crashes* 21 | - Games that have multiple supported languages or multiple discs may not "install" all the files and the dumper will hang trying to copy it 22 | - Can also apparently happen if the disc is dirty and not installing correctly when you start the dump 23 | - Current workaround is to make sure you have all the languages and all the discs completely installed prior to dumping 24 | - Thoughts: Is there a PFS flag that mark these files? Or is there some other way to detect "optional" files? 25 | 26 | \* These currently have work arounds in the dumper built into the homebrew store 27 | 28 | ## TODO 29 | 30 | - [ ] Fix known issues listed above 31 | - [X] NpBind 32 | - [X] Implementation 33 | - [X] Tests 34 | - [ ] SFO 35 | - [X] Implementation 36 | - [ ] Tests 37 | - [ ] GP4 38 | - [ ] Implementation 39 | - [X] Basic GP4 Generation 40 | - [ ] PlayGo Related Issues (Want to match retail PKG settings) 41 | - [ ] PFS Compress Option (Want to match retail PKG settings) 42 | - [ ] Tests 43 | - [ ] PKG 44 | - [ ] Implementation 45 | - [X] Basic 46 | - [ ] Decrypt encrypted Sc0 entries 47 | - [ ] `is_pkg` and `is_fpkg` functions 48 | - [ ] Tests 49 | - [ ] PFS 50 | - [X] Implementation 51 | - [ ] Tests 52 | - [ ] Decrypt SELF 53 | - [X] Implementation 54 | - [ ] Tests 55 | - [ ] FSELF 56 | - [ ] Implementation 57 | - Zero'd ELF header(?) 58 | - [ ] Tests 59 | - [ ] RIF (For Additional Content w/o Data & Entitlement Keys) 60 | - [ ] Implementation 61 | - [ ] Tests 62 | - [ ] Dump 63 | - [X] Base 64 | - [X] Patch 65 | - [X] Additional Content w/ Data 66 | - [X] Theme 67 | - [X] Retail Theme + Theme "Unlock" 68 | - [X] Remaster 69 | - [ ] Build a script to diff the actual files with the original content PC side to make a patch PKG vs a Remaster (?) 70 | - [ ] Additional Content w/o Data 71 | - [ ] Multi-Disc (Does it just work without changes?) 72 | - [ ] Verification 73 | - [ ] Save encrypted/modified files for backup/preservation purposes 74 | - [X] Encrypted Binary (FILENAME.EXT.encrypted) 75 | - [X] Decrypted Binary (FILENAME.EXT) 76 | - [X] FSELF (FILENAME.fself) 77 | - [X] Encrypted sc0 entries (FILENAME.EXT.encrypted) 78 | - [ ] Decrypted sc0 entries (FILENAME.EXT) 79 | - [X] Check decrypted binaries vs hash in encrypted header 80 | - [X] SFV-like file to compare vs known good rips 81 | - [ ] External service to verify/submit rip data 82 | - [ ] Optional submission to database 83 | - [ ] No actual data downloaded/uploaded, only metadata 84 | - [ ] Remove extra/unnecessary checks 85 | - ex. Calling `std::filesystem::is_regular_file` and `elf::is_self` together. Because `elf::is_self` calls `std::filesystem::is_regular_file` on the input itself 86 | - [ ] Split into self contained, read/write, individual libraries to be used in other applications 87 | - [ ] UI integration 88 | - Dump via dump options in main menu 89 | - Progress shown within notifications 90 | - Errors via error dialog 91 | -------------------------------------------------------------------------------- /include/common.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021-2022 Al Azif 2 | // License: GPLv3 3 | 4 | #ifndef DUMPER_INCLUDE_COMMON_H_ 5 | #define DUMPER_INCLUDE_COMMON_H_ 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #ifndef PAGE_SIZE 12 | #define PAGE_SIZE 0x4000 13 | #endif 14 | 15 | #ifndef UNUSED 16 | #define UNUSED(x) (void)(x) 17 | #endif 18 | 19 | #define FATAL_ERROR(error_message) \ 20 | { \ 21 | std::stringstream compiled_msg; \ 22 | compiled_msg << "Error: " << error_message << " at " << std::filesystem::path(__FILE__).filename() << ":" << __LINE__ << ":(" << __FUNCTION__ << ")"; \ 23 | throw std::runtime_error(compiled_msg.str()); \ 24 | }; 25 | 26 | #define DEBUG_LOG(log_message) \ 27 | { \ 28 | std::stringstream compiled_msg; \ 29 | compiled_msg << "Debug: " << log_message << " at " << std::filesystem::path(__FILE__).filename() << ":" << __LINE__ << ":(" << __FUNCTION__ << ")"; \ 30 | std::cout << compiled_msg.str() << std::endl; \ 31 | }; 32 | 33 | #endif // DUMPER_INCLUDE_COMMON_H_ 34 | -------------------------------------------------------------------------------- /include/crc32.h: -------------------------------------------------------------------------------- 1 | // ////////////////////////////////////////////////////////// 2 | // crc32.h 3 | // Copyright (c) 2014,2015 Stephan Brumme. All rights reserved. 4 | // see http://create.stephan-brumme.com/disclaimer.html 5 | // 6 | 7 | #pragma once 8 | 9 | //#include "hash.h" 10 | #include 11 | 12 | // define fixed size integer types 13 | #ifdef _MSC_VER 14 | // Windows 15 | typedef unsigned __int8 uint8_t; 16 | typedef unsigned __int32 uint32_t; 17 | #else 18 | // GCC 19 | #include 20 | #endif 21 | 22 | /// compute CRC32 hash, based on Intel's Slicing-by-8 algorithm 23 | /** Usage: 24 | CRC32 crc32; 25 | std::string myHash = crc32("Hello World"); // std::string 26 | std::string myHash2 = crc32("How are you", 11); // arbitrary data, 11 bytes 27 | 28 | // or in a streaming fashion: 29 | 30 | CRC32 crc32; 31 | while (more data available) 32 | crc32.add(pointer to fresh data, number of new bytes); 33 | std::string myHash3 = crc32.getHash(); 34 | 35 | Note: 36 | You can find code for the faster Slicing-by-16 algorithm on my website, too: 37 | http://create.stephan-brumme.com/crc32/ 38 | Its unrolled version is about twice as fast but its look-up table doubled in size as well. 39 | */ 40 | class CRC32 //: public Hash 41 | { 42 | public: 43 | /// hash is 4 bytes long 44 | enum { HashBytes = 4 }; 45 | 46 | /// same as reset() 47 | CRC32(); 48 | 49 | /// compute CRC32 of a memory block 50 | std::string operator()(const void *data, size_t numBytes); 51 | /// compute CRC32 of a string, excluding final zero 52 | std::string operator()(const std::string &text); 53 | 54 | /// add arbitrary number of bytes 55 | void add(const void *data, size_t numBytes); 56 | 57 | /// return latest hash as 8 hex characters 58 | std::string getHash(); 59 | /// return latest hash as bytes 60 | void getHash(unsigned char buffer[HashBytes]); 61 | 62 | /// restart 63 | void reset(); 64 | 65 | private: 66 | /// hash 67 | uint32_t m_hash; 68 | }; 69 | -------------------------------------------------------------------------------- /include/dump.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021-2022 Al Azif 2 | // License: GPLv3 3 | 4 | #ifndef DUMPER_INCLUDE_DUMP_H_ 5 | #define DUMPER_INCLUDE_DUMP_H_ 6 | 7 | #include 8 | 9 | namespace dump { 10 | void __dump(const std::string &usb_device, const std::string &title_id, const std::string &type); 11 | void dump_base(const std::string &usb_device, const std::string &title_id); 12 | void dump_patch(const std::string &usb_device, const std::string &title_id); 13 | void dump_remaster(const std::string &usb_device, const std::string &title_id); 14 | void dump_theme(const std::string &usb_device, const std::string &theme_id); 15 | void dump_theme_unlock(const std::string &usb_device, const std::string &theme_id); 16 | void dump_ac(const std::string &usb_device, const std::string &ac_id); 17 | void dump_ac_no_data(const std::string &usb_device, const std::string &ac_id); 18 | } // namespace dump 19 | 20 | #endif // DUMPER_INCLUDE_DUMP_H_ 21 | -------------------------------------------------------------------------------- /include/elf.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021-2022 Al Azif 2 | // License: GPLv3 3 | 4 | #ifndef DUMPER_INCLUDE_ELF_H_ 5 | #define DUMPER_INCLUDE_ELF_H_ 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include "elf_common.h" 12 | #include "elf_structs.h" 13 | 14 | #define ELF_MAGIC 0x7F454C46 15 | #define SELF_MAGIC 0x4F153D1D 16 | 17 | #define MAP_SELF 0x80000 18 | #define PT_NID 0x61000000 19 | 20 | namespace elf { 21 | typedef struct { 22 | uint32_t props; 23 | uint32_t reserved; 24 | uint64_t offset; 25 | uint64_t file_size; 26 | uint64_t memory_size; 27 | } SelfEntry; 28 | 29 | // SELF Header from: https://www.psdevwiki.com/ps4/SELF_File_Format#SELF_Header_Structure 30 | typedef struct { 31 | uint32_t magic; // File magic 32 | 33 | // uint32_t unknown; // Always 00 01 01 12 34 | uint8_t version; 35 | uint8_t mode; 36 | uint8_t endian; 37 | uint8_t attr; 38 | 39 | unsigned char content_type; // 1 on Self, 4 on PUP Entry 40 | unsigned char program_type; // 0x0 PUP, 0x8 NPDRM Application, 0x9 PLUGIN, 0xC Kernel, 0xE Security Module, 0xF Secure Kernel 41 | uint16_t padding; // Padding 42 | uint16_t header_size; // Header size 43 | uint16_t signature_size; // Metadata size 44 | uint64_t self_size; // Size of SELF 45 | uint16_t num_of_segments; // Number of Segments, 1 Kernel, 2 SL and Modules, 4 Kernel ELFs, 6 .selfs, 2 .sdll, 6 .sprx, 6 ShellCore, 6 eboot.bin, 2 sexe 46 | uint16_t flags; // Always 0x22 47 | uint32_t reserved; // Reserved 48 | } SelfHeader; 49 | 50 | // SCE Header from: https://www.psdevwiki.com/ps4/SELF_File_Format#SCE_Special 51 | typedef struct { 52 | uint64_t program_authority_id; 53 | uint64_t program_type; 54 | uint64_t app_version; 55 | uint64_t fw_version; 56 | unsigned char digest[0x20]; 57 | } SceHeader; 58 | 59 | // SCE Header from: https://www.psdevwiki.com/ps4/SELF_File_Format#SCE_Special 60 | typedef struct { 61 | uint64_t program_authority_id; 62 | uint64_t program_type; 63 | uint64_t app_version; 64 | uint64_t fw_version; 65 | unsigned char digest[0x20]; 66 | unsigned char padding[0x10]; // TODO: There is something here besides just padding...? 67 | unsigned char content_id[0x14]; // TODO: Or is it 0x13 if you don't include the "-" 68 | unsigned char unknown[0xC]; // TODO: There appears to be more here...? 69 | } SceHeaderNpdrm; 70 | 71 | uint64_t get_sce_header_offset(const std::string &path); 72 | SceHeader get_sce_header(const std::string &path); 73 | SceHeaderNpdrm get_sce_header_npdrm(const std::string &path); 74 | bool is_elf(const std::string &path); 75 | bool is_self(const std::string &path); 76 | bool is_npdrm(const std::string &path); 77 | std::string get_ptype(const std::string &path); 78 | uint64_t get_paid(const std::string &path); 79 | uint64_t get_app_version(const std::string &path); 80 | uint64_t get_fw_version(const std::string &path); 81 | std::vector get_digest(const std::string &path); 82 | std::vector get_auth_info(const std::string &path); 83 | bool is_valid_decrypt(const std::string &original, const std::string &decrypted); 84 | void zero_section_header(const std::string &path); 85 | void decrypt(const std::string &input_path, const std::string &output_path); 86 | } // namespace elf 87 | 88 | #endif // DUMPER_INCLUDE_ELF_H_ 89 | -------------------------------------------------------------------------------- /include/elf_structs.h: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 1996-1998 John D. Polstra. 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24 | * SUCH DAMAGE. 25 | * 26 | * $FreeBSD: release/9.0.0/sys/sys/elf64.h 186667 2009-01-01 02:08:56Z obrien $ 27 | */ 28 | 29 | #ifndef ELF_STRUCTS_H_ 30 | #define ELF_STRUCTS_H_ 31 | 32 | #include 33 | 34 | #include "elf_common.h" 35 | 36 | /* 37 | * ELF definitions common to all 64-bit architectures. 38 | */ 39 | 40 | typedef uint64_t Elf64_Addr; 41 | typedef uint16_t Elf64_Half; 42 | typedef uint64_t Elf64_Off; 43 | typedef int32_t Elf64_Sword; 44 | typedef int64_t Elf64_Sxword; 45 | typedef uint32_t Elf64_Word; 46 | typedef uint64_t Elf64_Lword; 47 | typedef uint64_t Elf64_Xword; 48 | 49 | /* 50 | * Types of dynamic symbol hash table bucket and chain elements. 51 | * 52 | * This is inconsistent among 64 bit architectures, so a machine dependent 53 | * typedef is required. 54 | */ 55 | 56 | typedef Elf64_Word Elf64_Hashelt; 57 | 58 | /* Non-standard class-dependent datatype used for abstraction. */ 59 | typedef Elf64_Xword Elf64_Size; 60 | typedef Elf64_Sxword Elf64_Ssize; 61 | 62 | /* 63 | * ELF header. 64 | */ 65 | 66 | typedef struct { 67 | unsigned char e_ident[EI_NIDENT]; /* File identification. */ 68 | Elf64_Half e_type; /* File type. */ 69 | Elf64_Half e_machine; /* Machine architecture. */ 70 | Elf64_Word e_version; /* ELF format version. */ 71 | Elf64_Addr e_entry; /* Entry point. */ 72 | Elf64_Off e_phoff; /* Program header file offset. */ 73 | Elf64_Off e_shoff; /* Section header file offset. */ 74 | Elf64_Word e_flags; /* Architecture-specific flags. */ 75 | Elf64_Half e_ehsize; /* Size of ELF header in bytes. */ 76 | Elf64_Half e_phentsize; /* Size of program header entry. */ 77 | Elf64_Half e_phnum; /* Number of program header entries. */ 78 | Elf64_Half e_shentsize; /* Size of section header entry. */ 79 | Elf64_Half e_shnum; /* Number of section header entries. */ 80 | Elf64_Half e_shstrndx; /* Section name strings section. */ 81 | } Elf64_Ehdr; 82 | 83 | /* 84 | * Section header. 85 | */ 86 | 87 | typedef struct { 88 | Elf64_Word sh_name; /* Section name (index into the 89 | section header string table). */ 90 | Elf64_Word sh_type; /* Section type. */ 91 | Elf64_Xword sh_flags; /* Section flags. */ 92 | Elf64_Addr sh_addr; /* Address in memory image. */ 93 | Elf64_Off sh_offset; /* Offset in file. */ 94 | Elf64_Xword sh_size; /* Size in bytes. */ 95 | Elf64_Word sh_link; /* Index of a related section. */ 96 | Elf64_Word sh_info; /* Depends on section type. */ 97 | Elf64_Xword sh_addralign; /* Alignment in bytes. */ 98 | Elf64_Xword sh_entsize; /* Size of each entry in section. */ 99 | } Elf64_Shdr; 100 | 101 | /* 102 | * Program header. 103 | */ 104 | 105 | typedef struct { 106 | Elf64_Word p_type; /* Entry type. */ 107 | Elf64_Word p_flags; /* Access permission flags. */ 108 | Elf64_Off p_offset; /* File offset of contents. */ 109 | Elf64_Addr p_vaddr; /* Virtual address in memory image. */ 110 | Elf64_Addr p_paddr; /* Physical address (not used). */ 111 | Elf64_Xword p_filesz; /* Size of contents in file. */ 112 | Elf64_Xword p_memsz; /* Size of contents in memory. */ 113 | Elf64_Xword p_align; /* Alignment in memory and file. */ 114 | } Elf64_Phdr; 115 | 116 | /* 117 | * Dynamic structure. The ".dynamic" section contains an array of them. 118 | */ 119 | 120 | typedef struct { 121 | Elf64_Sxword d_tag; /* Entry type. */ 122 | union { 123 | Elf64_Xword d_val; /* Integer value. */ 124 | Elf64_Addr d_ptr; /* Address value. */ 125 | } d_un; 126 | } Elf64_Dyn; 127 | 128 | /* 129 | * Relocation entries. 130 | */ 131 | 132 | /* Relocations that don't need an addend field. */ 133 | typedef struct { 134 | Elf64_Addr r_offset; /* Location to be relocated. */ 135 | Elf64_Xword r_info; /* Relocation type and symbol index. */ 136 | } Elf64_Rel; 137 | 138 | /* Relocations that need an addend field. */ 139 | typedef struct { 140 | Elf64_Addr r_offset; /* Location to be relocated. */ 141 | Elf64_Xword r_info; /* Relocation type and symbol index. */ 142 | Elf64_Sxword r_addend; /* Addend. */ 143 | } Elf64_Rela; 144 | 145 | /* Macros for accessing the fields of r_info. */ 146 | #define ELF64_R_SYM(info) ((info) >> 32) 147 | #define ELF64_R_TYPE(info) ((info)&0xffffffffL) 148 | 149 | /* Macro for constructing r_info from field values. */ 150 | #define ELF64_R_INFO(sym, type) (((sym) << 32) + ((type)&0xffffffffL)) 151 | 152 | #define ELF64_R_TYPE_DATA(info) (((Elf64_Xword)(info) << 32) >> 40) 153 | #define ELF64_R_TYPE_ID(info) (((Elf64_Xword)(info) << 56) >> 56) 154 | #define ELF64_R_TYPE_INFO(data, type) \ 155 | (((Elf64_Xword)(data) << 8) + (Elf64_Xword)(type)) 156 | 157 | /* 158 | * Note entry header 159 | */ 160 | typedef Elf_Note Elf64_Nhdr; 161 | 162 | /* 163 | * Move entry 164 | */ 165 | typedef struct { 166 | Elf64_Lword m_value; /* symbol value */ 167 | Elf64_Xword m_info; /* size + index */ 168 | Elf64_Xword m_poffset; /* symbol offset */ 169 | Elf64_Half m_repeat; /* repeat count */ 170 | Elf64_Half m_stride; /* stride info */ 171 | } Elf64_Move; 172 | 173 | #define ELF64_M_SYM(info) ((info) >> 8) 174 | #define ELF64_M_SIZE(info) ((unsigned char)(info)) 175 | #define ELF64_M_INFO(sym, size) (((sym) << 8) + (unsigned char)(size)) 176 | 177 | /* 178 | * Hardware/Software capabilities entry 179 | */ 180 | typedef struct { 181 | Elf64_Xword c_tag; /* how to interpret value */ 182 | union { 183 | Elf64_Xword c_val; 184 | Elf64_Addr c_ptr; 185 | } c_un; 186 | } Elf64_Cap; 187 | 188 | /* 189 | * Symbol table entries. 190 | */ 191 | 192 | typedef struct { 193 | Elf64_Word st_name; /* String table index of name. */ 194 | unsigned char st_info; /* Type and binding information. */ 195 | unsigned char st_other; /* Reserved (not used). */ 196 | Elf64_Half st_shndx; /* Section index of symbol. */ 197 | Elf64_Addr st_value; /* Symbol value. */ 198 | Elf64_Xword st_size; /* Size of associated object. */ 199 | } Elf64_Sym; 200 | 201 | /* Macros for accessing the fields of st_info. */ 202 | #define ELF64_ST_BIND(info) ((info) >> 4) 203 | #define ELF64_ST_TYPE(info) ((info)&0xf) 204 | 205 | /* Macro for constructing st_info from field values. */ 206 | #define ELF64_ST_INFO(bind, type) (((bind) << 4) + ((type)&0xf)) 207 | 208 | /* Macro for accessing the fields of st_other. */ 209 | #define ELF64_ST_VISIBILITY(oth) ((oth)&0x3) 210 | 211 | /* Structures used by Sun & GNU-style symbol versioning. */ 212 | typedef struct { 213 | Elf64_Half vd_version; 214 | Elf64_Half vd_flags; 215 | Elf64_Half vd_ndx; 216 | Elf64_Half vd_cnt; 217 | Elf64_Word vd_hash; 218 | Elf64_Word vd_aux; 219 | Elf64_Word vd_next; 220 | } Elf64_Verdef; 221 | 222 | typedef struct { 223 | Elf64_Word vda_name; 224 | Elf64_Word vda_next; 225 | } Elf64_Verdaux; 226 | 227 | typedef struct { 228 | Elf64_Half vn_version; 229 | Elf64_Half vn_cnt; 230 | Elf64_Word vn_file; 231 | Elf64_Word vn_aux; 232 | Elf64_Word vn_next; 233 | } Elf64_Verneed; 234 | 235 | typedef struct { 236 | Elf64_Word vna_hash; 237 | Elf64_Half vna_flags; 238 | Elf64_Half vna_other; 239 | Elf64_Word vna_name; 240 | Elf64_Word vna_next; 241 | } Elf64_Vernaux; 242 | 243 | typedef Elf64_Half Elf64_Versym; 244 | 245 | typedef struct { 246 | Elf64_Half si_boundto; /* direct bindings - symbol bound to */ 247 | Elf64_Half si_flags; /* per symbol flags */ 248 | } Elf64_Syminfo; 249 | 250 | #endif // ELF_STRUCTS_H_ 251 | -------------------------------------------------------------------------------- /include/fself.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021-2022 Al Azif 2 | // License: GPLv3 3 | 4 | #ifndef DUMPER_INCLUDE_FSELF_H_ 5 | #define DUMPER_INCLUDE_FSELF_H_ 6 | 7 | #include 8 | #include 9 | 10 | namespace fself { 11 | bool is_fself(const std::string &path); 12 | void make_fself(const std::string &input, const std::string &output, uint64_t paid, const std::string &ptype, uint64_t app_version, uint64_t fw_version, std::vector auth_info); 13 | void un_fself(const std::string &input, const std::string &output); 14 | } // namespace fself 15 | 16 | #endif // DUMPER_INCLUDE_FSELF_H_ 17 | -------------------------------------------------------------------------------- /include/gp4.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021-2022 Al Azif 2 | // License: GPLv3 3 | 4 | #ifndef DUMPER_INCLUDE_GP4_H_ 5 | #define DUMPER_INCLUDE_GP4_H_ 6 | 7 | #include 8 | #include 9 | 10 | #include "pugixml.hpp" 11 | 12 | namespace gp4 { 13 | void recursive_directory(const std::string &path, pugi::xml_node &node, bool validation = false); 14 | pugi::xml_document make_volume(const std::string &content_id, const std::string &volume_type, std::string c_date = "", std::string c_time = ""); 15 | pugi::xml_document make_playgo(const std::string &playgo_xml); 16 | pugi::xml_document make_files(const std::string &path, bool validation = false); 17 | pugi::xml_document make_directories(const std::string &path, bool validation = false); 18 | pugi::xml_document assemble(const pugi::xml_document &volume, const pugi::xml_document &playgo, const pugi::xml_document &files, const pugi::xml_document &directories, const std::string &custom_version = ""); 19 | void write(const pugi::xml_document &xml, const std::string &path); 20 | void generate(const std::string &sfo_path, const std::string &output_path, const std::string &gp4_path, const std::string &type, bool validation = false); 21 | } // namespace gp4 22 | 23 | #endif // DUMPER_INCLUDE_GP4_H_ 24 | -------------------------------------------------------------------------------- /include/md5.h: -------------------------------------------------------------------------------- 1 | // ////////////////////////////////////////////////////////// 2 | // md5.h 3 | // Copyright (c) 2014 Stephan Brumme. All rights reserved. 4 | // see http://create.stephan-brumme.com/disclaimer.html 5 | // 6 | 7 | #pragma once 8 | 9 | //#include "hash.h" 10 | #include 11 | 12 | // define fixed size integer types 13 | #ifdef _MSC_VER 14 | // Windows 15 | typedef unsigned __int8 uint8_t; 16 | typedef unsigned __int32 uint32_t; 17 | typedef unsigned __int64 uint64_t; 18 | #else 19 | // GCC 20 | #include 21 | #endif 22 | 23 | /// compute MD5 hash 24 | /** Usage: 25 | MD5 md5; 26 | std::string myHash = md5("Hello World"); // std::string 27 | std::string myHash2 = md5("How are you", 11); // arbitrary data, 11 bytes 28 | 29 | // or in a streaming fashion: 30 | 31 | MD5 md5; 32 | while (more data available) 33 | md5.add(pointer to fresh data, number of new bytes); 34 | std::string myHash3 = md5.getHash(); 35 | */ 36 | class MD5 //: public Hash 37 | { 38 | public: 39 | /// split into 64 byte blocks (=> 512 bits), hash is 16 bytes long 40 | enum { BlockSize = 512 / 8, 41 | HashBytes = 16 }; 42 | 43 | /// same as reset() 44 | MD5(); 45 | 46 | /// compute MD5 of a memory block 47 | std::string operator()(const void *data, size_t numBytes); 48 | /// compute MD5 of a string, excluding final zero 49 | std::string operator()(const std::string &text); 50 | 51 | /// add arbitrary number of bytes 52 | void add(const void *data, size_t numBytes); 53 | 54 | /// return latest hash as 32 hex characters 55 | std::string getHash(); 56 | /// return latest hash as bytes 57 | void getHash(unsigned char buffer[HashBytes]); 58 | 59 | /// restart 60 | void reset(); 61 | 62 | private: 63 | /// process 64 bytes 64 | void processBlock(const void *data); 65 | /// process everything left in the internal buffer 66 | void processBuffer(); 67 | 68 | /// size of processed data in bytes 69 | uint64_t m_numBytes; 70 | /// valid bytes in m_buffer 71 | size_t m_bufferSize; 72 | /// bytes not processed yet 73 | uint8_t m_buffer[BlockSize]; 74 | 75 | enum { HashValues = HashBytes / 4 }; 76 | /// hash, stored as integers 77 | uint32_t m_hash[HashValues]; 78 | }; 79 | -------------------------------------------------------------------------------- /include/npbind.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021-2022 Al Azif 2 | // License: GPLv3 3 | 4 | #ifndef DUMPER_INCLUDE_NPBIND_H_ 5 | #define DUMPER_INCLUDE_NPBIND_H_ 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #define NPBIND_MAGIC 0xD294A018 12 | 13 | namespace npbind { 14 | typedef struct { 15 | uint32_t magic; 16 | uint32_t version; 17 | uint64_t file_size; 18 | uint64_t entry_size; 19 | uint64_t num_entries; 20 | char padding[0x60]; 21 | } NpBindHeader; 22 | 23 | typedef struct { 24 | uint16_t type; 25 | uint16_t size; 26 | char data[0xC]; 27 | } NpBindNpCommIdEntry; 28 | 29 | typedef struct { 30 | uint16_t type; 31 | uint16_t size; 32 | char data[0xC]; 33 | } NpBindTrophyNumberEntry; 34 | 35 | typedef struct { 36 | uint16_t type; 37 | uint16_t size; 38 | unsigned char data[0xB0]; 39 | } NpBindUnk1Entry; 40 | 41 | typedef struct { 42 | uint16_t type; 43 | uint16_t size; 44 | unsigned char data[0x10]; 45 | } NpBindUnk2Entry; 46 | 47 | typedef struct { 48 | NpBindNpCommIdEntry npcommid; 49 | NpBindTrophyNumberEntry trophy_number; 50 | NpBindUnk1Entry unk1; 51 | NpBindUnk2Entry unk2; 52 | char padding[0x98]; 53 | } NpBindEntry; 54 | 55 | std::vector read(const std::string &path); // Flawfinder: ignore 56 | } // namespace npbind 57 | 58 | #endif // DUMPER_INCLUDE_NPBIND_H_ 59 | -------------------------------------------------------------------------------- /include/pfs.h: -------------------------------------------------------------------------------- 1 | #ifndef DUMPER_INCLUDE_PFS_H_ 2 | #define DUMPER_INCLUDE_PFS_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #define PFS_MAGIC 0x0B2A330100000000 9 | 10 | #define PFS_DUMP_BUFFER 0x4000 11 | 12 | namespace pfs { 13 | typedef struct { 14 | uint64_t version; 15 | uint64_t magic; 16 | uint32_t id[2]; 17 | char fmode; 18 | char clean; 19 | char ronly; 20 | char rsv; 21 | uint16_t mode; 22 | uint16_t unk1; 23 | uint32_t blocksz; 24 | uint32_t nbackup; 25 | uint64_t nblock; 26 | uint64_t ndinode; 27 | uint64_t ndblock; 28 | uint64_t ndinodeblock; 29 | uint64_t superroot_ino; 30 | } pfs_header; 31 | 32 | typedef struct { 33 | uint16_t mode; 34 | uint16_t nlink; 35 | uint32_t flags; 36 | uint64_t size; 37 | uint64_t size_compressed; 38 | uint64_t unix_time[4]; 39 | uint32_t time_nsec[4]; 40 | uint32_t uid; 41 | uint32_t gid; 42 | uint64_t spare[2]; 43 | uint32_t blocks; 44 | uint32_t db[12]; 45 | uint32_t ib[5]; 46 | } di_d32; 47 | 48 | typedef struct { 49 | uint32_t ino; 50 | uint32_t type; 51 | uint32_t namelen; 52 | uint32_t entsize; 53 | } dirent_t; 54 | 55 | extern pfs_header header; 56 | extern size_t pfs_size; 57 | extern size_t pfs_copied; 58 | extern std::vector inodes; 59 | 60 | extern std::ifstream pfs_input; 61 | 62 | void __parse_directory(uint32_t ino, uint32_t level, const std::string &output_path, bool calculate_only); 63 | void calculate_pfs_size(uint32_t ino, uint32_t level); 64 | void dump_pfs(uint32_t ino, uint32_t level, const std::string &output_path); 65 | void extract(const std::string &pfs_path, const std::string &output_path); 66 | } // namespace pfs 67 | 68 | #endif // DUMPER_INCLUDE_PFS_H_ 69 | -------------------------------------------------------------------------------- /include/pkg.h: -------------------------------------------------------------------------------- 1 | #ifndef DUMPER_INCLUDE_PKG_H_ 2 | #define DUMPER_INCLUDE_PKG_H_ 3 | 4 | #include 5 | #include 6 | 7 | #define PKG_MAGIC 0x7F434E54 8 | 9 | namespace pkg { 10 | // Struct from LibOrbisPKG 11 | typedef struct { 12 | uint32_t magic; 13 | uint32_t flags; 14 | uint32_t unk_0x08; 15 | uint32_t unk_0x0C; // 0x0F 16 | uint32_t entry_count; 17 | uint16_t sc_entry_count; 18 | uint16_t entry_count_2; // Same as entry_count 19 | uint32_t entry_table_offset; 20 | uint32_t main_ent_data_size; 21 | uint64_t body_offset; 22 | uint64_t body_size; 23 | unsigned char padding1[0x10]; 24 | char content_id[0x30]; // PKG_CONTENT_ID_SIZE 25 | uint32_t drm_type; 26 | uint32_t content_type; 27 | uint32_t content_flags; 28 | uint32_t promote_size; 29 | uint32_t version_date; 30 | uint32_t version_hash; 31 | uint32_t unk_0x88; // For delta patches only? 32 | uint32_t unk_0x8C; // For delta patches only? 33 | uint32_t unk_0x90; // For delta patches only? 34 | uint32_t unk_0x94; // For delta patches only? 35 | uint32_t iro_tag; 36 | uint32_t ekc_version; // DRM type version 37 | unsigned char padding2[0x60]; 38 | unsigned char sc_entries1_hash[0x20]; //PKG_HASH_SIZE 39 | unsigned char sc_entries2_hash[0x20]; // PKG_HASH_SIZE 40 | unsigned char digest_table_hash[0x20]; // PKG_HASH_SIZE 41 | unsigned char body_digest[0x20]; // PKG_HASH_SIZE 42 | unsigned char padding3[0x280]; 43 | // TODO: I think these fields are actually members of element of container array 44 | uint32_t unk_0x400; 45 | uint32_t pfs_image_count; 46 | uint64_t pfs_flags; 47 | uint64_t pfs_image_offset; 48 | uint64_t pfs_image_size; 49 | uint64_t mount_image_offset; 50 | uint64_t mount_image_size; 51 | uint64_t package_size; 52 | uint32_t pfs_signed_size; 53 | uint32_t pfs_cache_size; 54 | unsigned char pfs_image_digest[0x20]; // PKG_HASH_SIZE 55 | unsigned char pfs_signed_digest[0x20]; // PKG_HASH_SIZE 56 | uint64_t pfs_split_size_nth_0; 57 | uint64_t pfs_split_size_nth_1; 58 | unsigned char padding4[0xB50]; 59 | unsigned char package_digest[32]; 60 | unsigned char package_signature[256]; 61 | } PkgHeader; 62 | 63 | // struct from LibOrbisPKG 64 | typedef struct { 65 | uint32_t id; 66 | uint32_t name_table_offset; 67 | uint32_t flags1; 68 | uint32_t flags2; 69 | uint32_t offset; 70 | uint32_t size; 71 | uint64_t padding; 72 | } PkgTableEntry; 73 | 74 | bool is_pkg(const std::string &path); 75 | bool is_fpkg(const std::string &path); 76 | std::string get_entry_name_by_type(uint32_t type); 77 | void extract_sc0(const std::string &pkg_path, const std::string &output_path); 78 | } // namespace pkg 79 | 80 | #endif // DUMPER_INCLUDE_PKG_H_ 81 | -------------------------------------------------------------------------------- /include/pugiconfig.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * pugixml parser - version 1.12 3 | * -------------------------------------------------------- 4 | * Copyright (C) 2006-2022, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com) 5 | * Report bugs and download new versions at https://pugixml.org/ 6 | * 7 | * This library is distributed under the MIT License. See notice at the end 8 | * of this file. 9 | * 10 | * This work is based on the pugxml parser, which is: 11 | * Copyright (C) 2003, by Kristen Wegner (kristen@tima.net) 12 | */ 13 | 14 | #ifndef HEADER_PUGICONFIG_HPP 15 | #define HEADER_PUGICONFIG_HPP 16 | 17 | // Uncomment this to enable wchar_t mode 18 | // #define PUGIXML_WCHAR_MODE 19 | 20 | // Uncomment this to enable compact mode 21 | // #define PUGIXML_COMPACT 22 | 23 | // Uncomment this to disable XPath 24 | // #define PUGIXML_NO_XPATH 25 | 26 | // Uncomment this to disable STL 27 | // #define PUGIXML_NO_STL 28 | 29 | // Uncomment this to disable exceptions 30 | // #define PUGIXML_NO_EXCEPTIONS 31 | 32 | // Set this to control attributes for public classes/functions, i.e.: 33 | // #define PUGIXML_API __declspec(dllexport) // to export all public symbols from DLL 34 | // #define PUGIXML_CLASS __declspec(dllimport) // to import all classes from DLL 35 | // #define PUGIXML_FUNCTION __fastcall // to set calling conventions to all public functions to fastcall 36 | // In absence of PUGIXML_CLASS/PUGIXML_FUNCTION definitions PUGIXML_API is used instead 37 | 38 | // Tune these constants to adjust memory-related behavior 39 | // #define PUGIXML_MEMORY_PAGE_SIZE 32768 40 | // #define PUGIXML_MEMORY_OUTPUT_STACK 10240 41 | // #define PUGIXML_MEMORY_XPATH_PAGE_SIZE 4096 42 | 43 | // Tune this constant to adjust max nesting for XPath queries 44 | // #define PUGIXML_XPATH_DEPTH_LIMIT 1024 45 | 46 | // Uncomment this to switch to header-only version 47 | // #define PUGIXML_HEADER_ONLY 48 | 49 | // Uncomment this to enable long long support 50 | // #define PUGIXML_HAS_LONG_LONG 51 | 52 | #endif 53 | 54 | /** 55 | * Copyright (c) 2006-2022 Arseny Kapoulkine 56 | * 57 | * Permission is hereby granted, free of charge, to any person 58 | * obtaining a copy of this software and associated documentation 59 | * files (the "Software"), to deal in the Software without 60 | * restriction, including without limitation the rights to use, 61 | * copy, modify, merge, publish, distribute, sublicense, and/or sell 62 | * copies of the Software, and to permit persons to whom the 63 | * Software is furnished to do so, subject to the following 64 | * conditions: 65 | * 66 | * The above copyright notice and this permission notice shall be 67 | * included in all copies or substantial portions of the Software. 68 | * 69 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 70 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 71 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 72 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 73 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 74 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 75 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 76 | * OTHER DEALINGS IN THE SOFTWARE. 77 | */ 78 | -------------------------------------------------------------------------------- /include/sfo.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021-2022 Al Azif 2 | // License: GPLv3 3 | 4 | #ifndef DUMPER_INCLUDE_SFO_H_ 5 | #define DUMPER_INCLUDE_SFO_H_ 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #define SFO_MAGIC 0x00505346 12 | 13 | namespace sfo { 14 | typedef struct { 15 | uint32_t magic; 16 | uint32_t version; 17 | uint32_t key_table_offset; 18 | uint32_t data_table_offset; 19 | uint32_t num_entries; 20 | } SfoHeader; 21 | 22 | typedef struct { 23 | std::string key_name; 24 | uint16_t key_offset; 25 | uint16_t format; 26 | uint32_t length; 27 | uint32_t max_length; 28 | uint32_t data_offset; 29 | std::vector data; 30 | } SfoData; 31 | 32 | typedef struct { 33 | std::string key_name; 34 | std::string value; 35 | } SfoPubtoolinfoIndex; 36 | 37 | bool is_sfo(const std::string &path); 38 | std::vector read(const std::string &path); 39 | std::vector get_keys(const std::vector &data); 40 | uint16_t get_format(const std::string &key, const std::vector &data); 41 | uint32_t get_length(const std::string &key, const std::vector &data); 42 | uint32_t get_max_length(const std::string &key, const std::vector &data); 43 | std::vector get_value(const std::string &key, const std::vector &data); 44 | std::vector read_pubtool_data(const std::vector &data); 45 | std::vector get_pubtool_keys(const std::vector &data); 46 | std::string get_pubtool_value(const std::string &key, const std::vector &data); 47 | SfoData build_data(const std::string &key_name, const std::string &format, uint32_t length, uint32_t max_length, const std::vector &data); 48 | SfoPubtoolinfoIndex build_pubtool_data(const std::string &key, const std::string &value); 49 | std::vector add_data(const SfoData &add_data, const std::vector ¤t_data); 50 | std::vector add_pubtool_data(const SfoPubtoolinfoIndex &add_data, const std::vector ¤t_data); 51 | std::vector remove_key(const std::string &remove_key, const std::vector ¤t_data); 52 | std::vector remove_pubtool_key(const std::string &remove_key, const std::vector ¤t_data); 53 | bool compare_sfo_data(SfoData data_1, SfoData data_2); 54 | void write(const std::vector &data, const std::string &path); 55 | } // namespace sfo 56 | 57 | #endif // DUMPER_INCLUDE_SFO_H_ 58 | -------------------------------------------------------------------------------- /include/sha1.h: -------------------------------------------------------------------------------- 1 | // ////////////////////////////////////////////////////////// 2 | // sha1.h 3 | // Copyright (c) 2014,2015 Stephan Brumme. All rights reserved. 4 | // see http://create.stephan-brumme.com/disclaimer.html 5 | // 6 | 7 | #pragma once 8 | 9 | //#include "hash.h" 10 | #include 11 | 12 | // define fixed size integer types 13 | #ifdef _MSC_VER 14 | // Windows 15 | typedef unsigned __int8 uint8_t; 16 | typedef unsigned __int32 uint32_t; 17 | typedef unsigned __int64 uint64_t; 18 | #else 19 | // GCC 20 | #include 21 | #endif 22 | 23 | /// compute SHA1 hash 24 | /** Usage: 25 | SHA1 sha1; 26 | std::string myHash = sha1("Hello World"); // std::string 27 | std::string myHash2 = sha1("How are you", 11); // arbitrary data, 11 bytes 28 | 29 | // or in a streaming fashion: 30 | 31 | SHA1 sha1; 32 | while (more data available) 33 | sha1.add(pointer to fresh data, number of new bytes); 34 | std::string myHash3 = sha1.getHash(); 35 | */ 36 | class SHA1 //: public Hash 37 | { 38 | public: 39 | /// split into 64 byte blocks (=> 512 bits), hash is 20 bytes long 40 | enum { BlockSize = 512 / 8, 41 | HashBytes = 20 }; 42 | 43 | /// same as reset() 44 | SHA1(); 45 | 46 | /// compute SHA1 of a memory block 47 | std::string operator()(const void *data, size_t numBytes); 48 | /// compute SHA1 of a string, excluding final zero 49 | std::string operator()(const std::string &text); 50 | 51 | /// add arbitrary number of bytes 52 | void add(const void *data, size_t numBytes); 53 | 54 | /// return latest hash as 40 hex characters 55 | std::string getHash(); 56 | /// return latest hash as bytes 57 | void getHash(unsigned char buffer[HashBytes]); 58 | 59 | /// restart 60 | void reset(); 61 | 62 | private: 63 | /// process 64 bytes 64 | void processBlock(const void *data); 65 | /// process everything left in the internal buffer 66 | void processBuffer(); 67 | 68 | /// size of processed data in bytes 69 | uint64_t m_numBytes; 70 | /// valid bytes in m_buffer 71 | size_t m_bufferSize; 72 | /// bytes not processed yet 73 | uint8_t m_buffer[BlockSize]; 74 | 75 | enum { HashValues = HashBytes / 4 }; 76 | /// hash, stored as integers 77 | uint32_t m_hash[HashValues]; 78 | }; 79 | -------------------------------------------------------------------------------- /include/sha256.h: -------------------------------------------------------------------------------- 1 | // ////////////////////////////////////////////////////////// 2 | // sha256.h 3 | // Copyright (c) 2014,2015 Stephan Brumme. All rights reserved. 4 | // see http://create.stephan-brumme.com/disclaimer.html 5 | // 6 | 7 | #pragma once 8 | 9 | //#include "hash.h" 10 | #include 11 | 12 | // define fixed size integer types 13 | #ifdef _MSC_VER 14 | // Windows 15 | typedef unsigned __int8 uint8_t; 16 | typedef unsigned __int32 uint32_t; 17 | typedef unsigned __int64 uint64_t; 18 | #else 19 | // GCC 20 | #include 21 | #endif 22 | 23 | /// compute SHA256 hash 24 | /** Usage: 25 | SHA256 sha256; 26 | std::string myHash = sha256("Hello World"); // std::string 27 | std::string myHash2 = sha256("How are you", 11); // arbitrary data, 11 bytes 28 | 29 | // or in a streaming fashion: 30 | 31 | SHA256 sha256; 32 | while (more data available) 33 | sha256.add(pointer to fresh data, number of new bytes); 34 | std::string myHash3 = sha256.getHash(); 35 | */ 36 | class SHA256 //: public Hash 37 | { 38 | public: 39 | /// split into 64 byte blocks (=> 512 bits), hash is 32 bytes long 40 | enum { BlockSize = 512 / 8, 41 | HashBytes = 32 }; 42 | 43 | /// same as reset() 44 | SHA256(); 45 | 46 | /// compute SHA256 of a memory block 47 | std::string operator()(const void *data, size_t numBytes); 48 | /// compute SHA256 of a string, excluding final zero 49 | std::string operator()(const std::string &text); 50 | 51 | /// add arbitrary number of bytes 52 | void add(const void *data, size_t numBytes); 53 | 54 | /// return latest hash as 64 hex characters 55 | std::string getHash(); 56 | /// return latest hash as bytes 57 | void getHash(unsigned char buffer[HashBytes]); 58 | 59 | /// restart 60 | void reset(); 61 | 62 | private: 63 | /// process 64 bytes 64 | void processBlock(const void *data); 65 | /// process everything left in the internal buffer 66 | void processBuffer(); 67 | 68 | /// size of processed data in bytes 69 | uint64_t m_numBytes; 70 | /// valid bytes in m_buffer 71 | size_t m_bufferSize; 72 | /// bytes not processed yet 73 | uint8_t m_buffer[BlockSize]; 74 | 75 | enum { HashValues = HashBytes / 4 }; 76 | /// hash, stored as integers 77 | uint32_t m_hash[HashValues]; 78 | }; 79 | -------------------------------------------------------------------------------- /src/dump.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021-2022 Al Azif 2 | // License: GPLv3 3 | 4 | #include "dump.h" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "common.h" 16 | #include "elf.h" 17 | #include "fself.h" 18 | #include "gp4.h" 19 | #include "npbind.h" 20 | #include "pfs.h" 21 | #include "pkg.h" 22 | 23 | namespace dump { 24 | void __dump(const std::string &usb_device, const std::string &title_id, const std::string &type) { 25 | if (type != "base" && type != "patch" && type != "remaster" && type != "theme" && type != "theme-unlock" && type != "additional-content-data" && type != "additional-content-no-data") { 26 | FATAL_ERROR("Unknown asset type"); 27 | } 28 | 29 | // TID vs CID and type checks 30 | if (type == "base" || type == "patch" || type == "remaster") { 31 | if (!std::regex_match(title_id, std::regex("^[A-Z]{4}\\d{5}$"))) { 32 | FATAL_ERROR("Malformed title ID"); 33 | } 34 | } else { 35 | if (!std::regex_match(title_id, std::regex("^[A-Z]{2}\\d{4}-[A-Z]{4}\\d{5}_\\d{2}-[A-Z0-9]{16}$"))) { 36 | FATAL_ERROR("Malformed content ID"); 37 | } 38 | } 39 | 40 | std::string output_directory = title_id; 41 | if (type == "patch") { 42 | output_directory += "-patch"; // Add "-patch" to "patch" types so it doesn't overlap with "base" 43 | } else if (type == "theme-unlock") { 44 | output_directory += "-unlock"; // Add "-unlock" to theme type so we can differentiate between "install" and "unlock" PKGs 45 | } 46 | 47 | // Create base path 48 | std::filesystem::path output_path(usb_device); 49 | output_path /= output_directory; 50 | 51 | // Make sure the base path is a directory or can be created 52 | if (!std::filesystem::is_directory(output_path) && !std::filesystem::create_directories(output_path)) { 53 | FATAL_ERROR("Unable to create output directory"); 54 | } 55 | 56 | // Check for .dumping semaphore 57 | std::filesystem::path dumping_semaphore(usb_device); 58 | dumping_semaphore /= output_directory + ".dumping"; 59 | if (std::filesystem::exists(dumping_semaphore)) { 60 | FATAL_ERROR("This dump is currently dumping or closed unexpectedly! Please delete existing dump to enable dumping."); 61 | } 62 | 63 | // Check for .complete semaphore 64 | std::filesystem::path complete_semaphore(usb_device); 65 | complete_semaphore /= output_directory + ".complete"; 66 | if (std::filesystem::exists(complete_semaphore)) { 67 | FATAL_ERROR("This dump has already been completed! Please delete existing dump to enable dumping."); 68 | } 69 | 70 | // Create .dumping semaphore 71 | std::ofstream dumping_sem_touch(dumping_semaphore); 72 | dumping_sem_touch.close(); 73 | if (std::filesystem::exists(dumping_semaphore)) { 74 | FATAL_ERROR("Unable to create dumping semaphore!"); 75 | } 76 | 77 | // Create "sce_sys" directory in the output directory 78 | std::filesystem::path sce_sys_path(output_path); 79 | sce_sys_path /= "sce_sys"; 80 | if (!std::filesystem::is_directory(sce_sys_path) && !std::filesystem::create_directories(sce_sys_path)) { 81 | FATAL_ERROR("Unable to create `sce_sys` directory"); 82 | } 83 | 84 | if (type == "theme-unlock" || type == "additional-content-no-data") { 85 | std::filesystem::path param_destination(sce_sys_path); 86 | param_destination /= "param.sfo"; 87 | 88 | if (type == "theme-unlock") { 89 | // Copy theme install PKG 90 | std::filesystem::path install_source("/user/addcont/I00000002"); 91 | install_source /= title_id; 92 | install_source /= "ac.pkg"; 93 | 94 | std::filesystem::path install_destination(usb_device); 95 | install_destination /= title_id + "-install.pkg"; 96 | 97 | // Copy param.sfo 98 | if (!std::filesystem::copy_file(install_source, install_destination, std::filesystem::copy_options::overwrite_existing)) { 99 | FATAL_ERROR("Unable to copy" + std::string(install_source) + " to " + std::string(install_destination)); 100 | } 101 | 102 | std::filesystem::path param_source("/system_data/priv/appmeta/addcont/I00000002"); 103 | param_source /= title_id; 104 | param_source /= "param.sfo"; 105 | 106 | if (!std::filesystem::copy_file(param_source, param_destination, std::filesystem::copy_options::overwrite_existing)) { 107 | FATAL_ERROR("Unable to copy" + std::string(param_source) + " to " + std::string(param_destination)); 108 | } 109 | } else { 110 | // TODO: Find and copy... or create... "param.sfo" for "additional-content-no-data" at param_destination 111 | } 112 | } else { // "base", "patch", "remaster", "theme", "additional-content-data" 113 | // UnPKG 114 | std::filesystem::path pkg_directory_path; 115 | pkg_directory_path /= "user"; 116 | if (type == "base" || type == "remaster") { 117 | pkg_directory_path /= "app"; 118 | pkg_directory_path /= title_id; 119 | pkg_directory_path /= "app.pkg"; 120 | } else if (type == "patch") { 121 | pkg_directory_path /= "patch"; 122 | pkg_directory_path /= title_id; 123 | pkg_directory_path /= "patch.pkg"; 124 | } else if (type == "theme") { 125 | pkg_directory_path /= "addcont/I00000002"; 126 | pkg_directory_path /= title_id; 127 | pkg_directory_path /= "ac.pkg"; 128 | } else if (type == "additional-content-data") { 129 | pkg_directory_path /= "addcont"; 130 | // This regex will match because of the checks at the beginning of the function 131 | std::smatch match; 132 | std::regex_search(title_id, match, std::regex("^[A-Z]{2}\\d{4}-([A-Z]{4}\\d{5})_\\d{2}-([A-Z0-9]{16})$")); 133 | pkg_directory_path /= match.str(1); 134 | pkg_directory_path /= match.str(2); 135 | pkg_directory_path /= "ac.pkg"; 136 | } 137 | 138 | // Detect if on extended storage and make pkg_path 139 | std::filesystem::path pkg_path; 140 | std::filesystem::path ext_path("/mnt/ext0"); 141 | ext_path += pkg_directory_path; 142 | if (std::filesystem::exists(ext_path) && std::filesystem::is_regular_file(ext_path)) { 143 | pkg_path = ext_path; 144 | } else { 145 | pkg_path = pkg_directory_path; 146 | } 147 | 148 | pkg::extract_sc0(pkg_path, sce_sys_path); 149 | 150 | // Trophy "decryption" 151 | std::filesystem::path npbind_file(sce_sys_path); 152 | npbind_file /= "npbind.dat"; 153 | if (std::filesystem::is_regular_file(npbind_file)) { 154 | std::vector npbind_entries = npbind::read(npbind_file); // Flawfinder: ignore 155 | 156 | for (auto &&entry : npbind_entries) { 157 | std::filesystem::path src("/user/trophy/conf"); 158 | src /= std::string(entry.npcommid.data); 159 | src /= "TROPHY.TRP"; 160 | 161 | std::filesystem::path dst(sce_sys_path); 162 | dst /= "trophy"; 163 | uint32_t zerofill = 0; 164 | if (std::strlen(entry.trophy_number.data) < 2) { // Flawfinder: ignore 165 | zerofill = 2 - std::strlen(entry.trophy_number.data); // Flawfinder: ignore 166 | } 167 | dst /= "trophy" + std::string(zerofill, '0') + std::string(entry.trophy_number.data) + ".trp"; 168 | 169 | if (std::filesystem::is_regular_file(src)) { 170 | if (!std::filesystem::copy_file(src, dst, std::filesystem::copy_options::overwrite_existing)) { 171 | FATAL_ERROR("Unable to copy" + std::string(src) + " to " + std::string(dst)); 172 | } 173 | } 174 | } 175 | } 176 | 177 | // UnPFS 178 | std::filesystem::path pfs_path("/mnt/sandbox/pfsmnt"); 179 | pfs_path /= title_id; 180 | if (type == "base" || type == "remaster") { 181 | pfs_path += "-app0-nest"; 182 | } else if (type == "patch") { 183 | pfs_path += "-patch0-nest"; 184 | } else if (type == "theme" || type == "additional-content-data") { 185 | pfs_path += "-ac-nest"; 186 | } 187 | pfs_path /= "pfs_image.dat"; 188 | 189 | pfs::extract(pfs_path, output_path); 190 | } 191 | 192 | // Generate GP4 193 | std::filesystem::path sfo_path(sce_sys_path); 194 | sfo_path /= "param.sfo"; 195 | 196 | std::filesystem::path gp4_path(output_path); 197 | gp4_path /= output_directory + ".gp4"; 198 | 199 | gp4::generate(sfo_path, output_path, gp4_path, type); 200 | 201 | // Vector of strings for locations of SELF files for decryption 202 | std::vector self_files; 203 | for (auto &&p : std::filesystem::recursive_directory_iterator(output_path)) { 204 | if (elf::is_self(p.path())) { 205 | self_files.push_back(p.path()); 206 | } 207 | } 208 | 209 | // Decrypt ELF files and make into FSELFs 210 | for (auto &&entry : self_files) { 211 | std::filesystem::path original_path("/mnt/sandbox/pfsmnt"); 212 | if (type == "base") { 213 | original_path /= title_id + "-app0"; 214 | } else if (type == "patch") { 215 | original_path /= title_id + "-patch0"; 216 | } else if (type == "theme-unlock") { 217 | original_path /= title_id + "-ac"; 218 | } 219 | original_path /= entry; 220 | 221 | std::filesystem::path encrypted_path(output_path); 222 | encrypted_path /= entry; 223 | encrypted_path += ".encrypted"; 224 | 225 | // Copy original_path to encrypted_path 226 | if (!std::filesystem::copy_file(original_path, encrypted_path, std::filesystem::copy_options::overwrite_existing)) { 227 | FATAL_ERROR("Unable to copy" + std::string(original_path) + " to " + std::string(encrypted_path)); 228 | } 229 | 230 | std::filesystem::path decrypted_path(output_path); 231 | decrypted_path /= entry; 232 | 233 | // Get proper Program Authority ID, App Version, Firmware Version, and Auth Info from `encrypted_path` 234 | uint64_t program_authority_id = elf::get_paid(encrypted_path); 235 | std::string ptype = "fake"; // elf::get_ptype(encrypted_path); 236 | uint64_t app_version = elf::get_app_version(encrypted_path); 237 | uint64_t fw_version = elf::get_fw_version(encrypted_path); 238 | std::vector auth_info = elf::get_auth_info(encrypted_path); 239 | 240 | if (fself::is_fself(encrypted_path)) { 241 | // SELF is actually already an FSELF, un_fself it and delete the original, we'll make a new FSELF later 242 | // We cannot get the original SELF in this case, we can't truely verify the decrypted ELF, and the various options for make_fself may be wrong because it's based off an FSELF someone made previously 243 | fself::un_fself(encrypted_path, decrypted_path); 244 | if (!elf::is_valid_decrypt(encrypted_path, decrypted_path)) { 245 | FATAL_ERROR("Invalid ELF decryption!"); 246 | } 247 | if (!std::filesystem::remove(encrypted_path)) { 248 | FATAL_ERROR("Unable to delete original FSELF"); 249 | } 250 | } else { 251 | // Decrypt and verify SELF 252 | elf::decrypt(encrypted_path, decrypted_path); 253 | if (!elf::is_valid_decrypt(encrypted_path, decrypted_path)) { 254 | FATAL_ERROR("Invalid ELF decryption!"); 255 | } 256 | } 257 | 258 | std::filesystem::path fself_path(decrypted_path); 259 | fself_path.replace_extension(".fself"); 260 | 261 | fself::make_fself(decrypted_path, fself_path, program_authority_id, ptype, app_version, fw_version, auth_info); 262 | } 263 | 264 | // Generate verification file 265 | std::filesystem::path validation_path(output_path); 266 | validation_path /= output_directory + ".gp4fv"; 267 | gp4::generate(sfo_path, output_path, validation_path, type, true); 268 | 269 | // Delete .dumping semaphore 270 | if (!std::filesystem::remove(dumping_semaphore)) { 271 | FATAL_ERROR("Unable to delete dumping semaphore"); 272 | } 273 | 274 | // Create .complete semaphore 275 | std::ofstream complete_sem_touch(complete_semaphore); 276 | complete_sem_touch.close(); 277 | if (std::filesystem::exists(complete_semaphore)) { 278 | FATAL_ERROR("Unable to create completion semaphore!"); 279 | } 280 | } 281 | 282 | void dump_base(const std::string &usb_device, const std::string &title_id) { 283 | __dump(usb_device, title_id, "base"); 284 | } 285 | 286 | void dump_patch(const std::string &usb_device, const std::string &title_id) { 287 | __dump(usb_device, title_id, "patch"); 288 | } 289 | 290 | void dump_remaster(const std::string &usb_device, const std::string &title_id) { 291 | __dump(usb_device, title_id, "remaster"); 292 | } 293 | 294 | void dump_theme(const std::string &usb_device, const std::string &theme_id) { 295 | __dump(usb_device, theme_id, "theme"); 296 | } 297 | 298 | void dump_theme_unlock(const std::string &usb_device, const std::string &theme_id) { 299 | __dump(usb_device, theme_id, "theme-unlock"); 300 | } 301 | 302 | void dump_ac(const std::string &usb_device, const std::string &ac_id) { 303 | __dump(usb_device, ac_id, "additional-content-data"); 304 | } 305 | 306 | void dump_ac_no_data(const std::string &usb_device, const std::string &ac_id) { 307 | __dump(usb_device, ac_id, "additional-content-no-data"); 308 | } 309 | } // namespace dump 310 | -------------------------------------------------------------------------------- /src/fself.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021-2022 Al Azif 2 | // License: GPLv3 3 | 4 | #include "fself.h" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "common.h" 14 | #include "elf.h" 15 | 16 | namespace fself { 17 | bool is_fself(const std::string &path) { 18 | // TODO 19 | 20 | UNUSED(path); 21 | 22 | return false; 23 | } 24 | 25 | void make_fself(const std::string &input, const std::string &output, uint64_t paid, const std::string &ptype, uint64_t app_version, uint64_t fw_version, std::vector auth_info) { 26 | // Check for empty or pure whitespace path 27 | if (input.empty() || std::all_of(input.begin(), input.end(), [](char c) { return std::isspace(c); })) { 28 | FATAL_ERROR("Empty input path argument!"); 29 | } 30 | 31 | // Check if file exists and is file 32 | if (!std::filesystem::is_regular_file(input)) { 33 | FATAL_ERROR("Input path does not exist or is not a file!"); 34 | } 35 | 36 | // Open path 37 | std::ifstream self_input(input, std::ios::in | std::ios::binary); 38 | if (!self_input || !self_input.good()) { 39 | self_input.close(); 40 | FATAL_ERROR("Cannot open file: " + std::string(input)); 41 | } 42 | 43 | // Check to make sure file is a ELF 44 | if (!elf::is_elf(input)) { 45 | self_input.close(); 46 | FATAL_ERROR("Input file is not an ELF!"); 47 | } 48 | 49 | // Check for empty or pure whitespace path 50 | if (output.empty() || std::all_of(output.begin(), output.end(), [](char c) { return std::isspace(c); })) { 51 | self_input.close(); 52 | FATAL_ERROR("Empty output path argument!"); 53 | } 54 | 55 | std::filesystem::path output_path(output); 56 | 57 | // Exists, but is not a file 58 | if (std::filesystem::exists(output_path) && !std::filesystem::is_regular_file(output_path)) { 59 | self_input.close(); 60 | FATAL_ERROR("Oputput object exists but is not a file!"); 61 | } 62 | 63 | // paid, app_version and fw_version are unsigned and any value between 0x0 and 0xFFFFFFFFFFFFFFFF is valid so we do not have to check range 64 | 65 | if (ptype != "fake" && ptype != "npdrm_exec" && ptype != "npdrm_dynlib" && ptype != "system_exec" && ptype != "system_dynlib" && ptype != "host_kernel" && ptype != "secure_module" && ptype != "secure_kernel") { 66 | self_input.close(); 67 | FATAL_ERROR("Invalid ptype!"); 68 | } 69 | 70 | // Should be 0x110 in size and 0-9a-fA-F 71 | if (auth_info.size() != 0x110) { 72 | self_input.close(); 73 | FATAL_ERROR("Auth info is invalid length!"); 74 | } 75 | if (std::all_of(output.begin(), output.end(), [](char c) { return std::isxdigit(c); })) { 76 | self_input.close(); 77 | FATAL_ERROR("Auth info is not hex!"); 78 | } 79 | 80 | // Input may not be "correct" but it's valid at this point 81 | 82 | // TODO 83 | self_input.close(); 84 | UNUSED(paid); 85 | UNUSED(app_version); 86 | UNUSED(fw_version); 87 | } 88 | 89 | void un_fself(const std::string &input, const std::string &output) { 90 | // TODO 91 | 92 | UNUSED(input); 93 | UNUSED(output); 94 | } 95 | } // namespace fself 96 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021-2022 Al Azif 2 | // License: GPLv3 3 | 4 | #if defined(__TEST__) 5 | 6 | #include 7 | 8 | #include "dump_test.h" 9 | #include "elf_test.h" 10 | #include "fself_test.h" 11 | #include "gp4_test.h" 12 | #include "npbind_test.h" 13 | #include "pfs_test.h" 14 | #include "pkg_test.h" 15 | #include "sfo_test.h" 16 | 17 | int main(int argc, char **argv) { 18 | ::testing::InitGoogleTest(&argc, argv); 19 | return RUN_ALL_TESTS(); 20 | } 21 | 22 | #else 23 | 24 | #include 25 | 26 | #include "common.h" 27 | #include "dump.h" 28 | 29 | int main() { 30 | std::string dump_dir = "/mnt/usb0"; 31 | dump::dump_base(dump_dir, "CUSA05258"); 32 | dump::dump_patch(dump_dir, "CUSA05258"); 33 | dump::dump_theme_unlock(dump_dir, "UP0700-CUSA05258_00-PS4TOBTHM0000001"); 34 | 35 | return 0; 36 | } 37 | 38 | #endif // __TEST__ 39 | -------------------------------------------------------------------------------- /src/md5.cpp: -------------------------------------------------------------------------------- 1 | // ////////////////////////////////////////////////////////// 2 | // md5.cpp 3 | // Copyright (c) 2014,2015 Stephan Brumme. All rights reserved. 4 | // see http://create.stephan-brumme.com/disclaimer.html 5 | // 6 | 7 | #include "md5.h" 8 | 9 | #ifndef _MSC_VER 10 | #include 11 | #endif 12 | 13 | /// same as reset() 14 | MD5::MD5() { 15 | reset(); 16 | } 17 | 18 | /// restart 19 | void MD5::reset() { 20 | m_numBytes = 0; 21 | m_bufferSize = 0; 22 | 23 | // according to RFC 1321 24 | m_hash[0] = 0x67452301; 25 | m_hash[1] = 0xefcdab89; 26 | m_hash[2] = 0x98badcfe; 27 | m_hash[3] = 0x10325476; 28 | } 29 | 30 | namespace { 31 | // mix functions for processBlock() 32 | inline uint32_t f1(uint32_t b, uint32_t c, uint32_t d) { 33 | return d ^ (b & (c ^ d)); // original: f = (b & c) | ((~b) & d); 34 | } 35 | 36 | inline uint32_t f2(uint32_t b, uint32_t c, uint32_t d) { 37 | return c ^ (d & (b ^ c)); // original: f = (b & d) | (c & (~d)); 38 | } 39 | 40 | inline uint32_t f3(uint32_t b, uint32_t c, uint32_t d) { 41 | return b ^ c ^ d; 42 | } 43 | 44 | inline uint32_t f4(uint32_t b, uint32_t c, uint32_t d) { 45 | return c ^ (b | ~d); 46 | } 47 | 48 | inline uint32_t rotate(uint32_t a, uint32_t c) { 49 | return (a << c) | (a >> (32 - c)); 50 | } 51 | 52 | #if defined(__BYTE_ORDER) && (__BYTE_ORDER != 0) && (__BYTE_ORDER == __BIG_ENDIAN) 53 | inline uint32_t swap(uint32_t x) { 54 | #if defined(__GNUC__) || defined(__clang__) 55 | return __builtin_bswap32(x); 56 | #endif 57 | #ifdef MSC_VER 58 | return _byteswap_ulong(x); 59 | #endif 60 | 61 | return (x >> 24) | 62 | ((x >> 8) & 0x0000FF00) | 63 | ((x << 8) & 0x00FF0000) | 64 | (x << 24); 65 | } 66 | #endif 67 | } // namespace 68 | 69 | /// process 64 bytes 70 | void MD5::processBlock(const void *data) { 71 | // get last hash 72 | uint32_t a = m_hash[0]; 73 | uint32_t b = m_hash[1]; 74 | uint32_t c = m_hash[2]; 75 | uint32_t d = m_hash[3]; 76 | 77 | // data represented as 16x 32-bit words 78 | const uint32_t *words = (uint32_t *)data; 79 | 80 | // computations are little endian, swap data if necessary 81 | #if defined(__BYTE_ORDER) && (__BYTE_ORDER != 0) && (__BYTE_ORDER == __BIG_ENDIAN) 82 | #define LITTLEENDIAN(x) swap(x) 83 | #else 84 | #define LITTLEENDIAN(x) (x) 85 | #endif 86 | 87 | // first round 88 | uint32_t word0 = LITTLEENDIAN(words[0]); 89 | a = rotate(a + f1(b, c, d) + word0 + 0xd76aa478, 7) + b; 90 | uint32_t word1 = LITTLEENDIAN(words[1]); 91 | d = rotate(d + f1(a, b, c) + word1 + 0xe8c7b756, 12) + a; 92 | uint32_t word2 = LITTLEENDIAN(words[2]); 93 | c = rotate(c + f1(d, a, b) + word2 + 0x242070db, 17) + d; 94 | uint32_t word3 = LITTLEENDIAN(words[3]); 95 | b = rotate(b + f1(c, d, a) + word3 + 0xc1bdceee, 22) + c; 96 | 97 | uint32_t word4 = LITTLEENDIAN(words[4]); 98 | a = rotate(a + f1(b, c, d) + word4 + 0xf57c0faf, 7) + b; 99 | uint32_t word5 = LITTLEENDIAN(words[5]); 100 | d = rotate(d + f1(a, b, c) + word5 + 0x4787c62a, 12) + a; 101 | uint32_t word6 = LITTLEENDIAN(words[6]); 102 | c = rotate(c + f1(d, a, b) + word6 + 0xa8304613, 17) + d; 103 | uint32_t word7 = LITTLEENDIAN(words[7]); 104 | b = rotate(b + f1(c, d, a) + word7 + 0xfd469501, 22) + c; 105 | 106 | uint32_t word8 = LITTLEENDIAN(words[8]); 107 | a = rotate(a + f1(b, c, d) + word8 + 0x698098d8, 7) + b; 108 | uint32_t word9 = LITTLEENDIAN(words[9]); 109 | d = rotate(d + f1(a, b, c) + word9 + 0x8b44f7af, 12) + a; 110 | uint32_t word10 = LITTLEENDIAN(words[10]); 111 | c = rotate(c + f1(d, a, b) + word10 + 0xffff5bb1, 17) + d; 112 | uint32_t word11 = LITTLEENDIAN(words[11]); 113 | b = rotate(b + f1(c, d, a) + word11 + 0x895cd7be, 22) + c; 114 | 115 | uint32_t word12 = LITTLEENDIAN(words[12]); 116 | a = rotate(a + f1(b, c, d) + word12 + 0x6b901122, 7) + b; 117 | uint32_t word13 = LITTLEENDIAN(words[13]); 118 | d = rotate(d + f1(a, b, c) + word13 + 0xfd987193, 12) + a; 119 | uint32_t word14 = LITTLEENDIAN(words[14]); 120 | c = rotate(c + f1(d, a, b) + word14 + 0xa679438e, 17) + d; 121 | uint32_t word15 = LITTLEENDIAN(words[15]); 122 | b = rotate(b + f1(c, d, a) + word15 + 0x49b40821, 22) + c; 123 | 124 | // second round 125 | a = rotate(a + f2(b, c, d) + word1 + 0xf61e2562, 5) + b; 126 | d = rotate(d + f2(a, b, c) + word6 + 0xc040b340, 9) + a; 127 | c = rotate(c + f2(d, a, b) + word11 + 0x265e5a51, 14) + d; 128 | b = rotate(b + f2(c, d, a) + word0 + 0xe9b6c7aa, 20) + c; 129 | 130 | a = rotate(a + f2(b, c, d) + word5 + 0xd62f105d, 5) + b; 131 | d = rotate(d + f2(a, b, c) + word10 + 0x02441453, 9) + a; 132 | c = rotate(c + f2(d, a, b) + word15 + 0xd8a1e681, 14) + d; 133 | b = rotate(b + f2(c, d, a) + word4 + 0xe7d3fbc8, 20) + c; 134 | 135 | a = rotate(a + f2(b, c, d) + word9 + 0x21e1cde6, 5) + b; 136 | d = rotate(d + f2(a, b, c) + word14 + 0xc33707d6, 9) + a; 137 | c = rotate(c + f2(d, a, b) + word3 + 0xf4d50d87, 14) + d; 138 | b = rotate(b + f2(c, d, a) + word8 + 0x455a14ed, 20) + c; 139 | 140 | a = rotate(a + f2(b, c, d) + word13 + 0xa9e3e905, 5) + b; 141 | d = rotate(d + f2(a, b, c) + word2 + 0xfcefa3f8, 9) + a; 142 | c = rotate(c + f2(d, a, b) + word7 + 0x676f02d9, 14) + d; 143 | b = rotate(b + f2(c, d, a) + word12 + 0x8d2a4c8a, 20) + c; 144 | 145 | // third round 146 | a = rotate(a + f3(b, c, d) + word5 + 0xfffa3942, 4) + b; 147 | d = rotate(d + f3(a, b, c) + word8 + 0x8771f681, 11) + a; 148 | c = rotate(c + f3(d, a, b) + word11 + 0x6d9d6122, 16) + d; 149 | b = rotate(b + f3(c, d, a) + word14 + 0xfde5380c, 23) + c; 150 | 151 | a = rotate(a + f3(b, c, d) + word1 + 0xa4beea44, 4) + b; 152 | d = rotate(d + f3(a, b, c) + word4 + 0x4bdecfa9, 11) + a; 153 | c = rotate(c + f3(d, a, b) + word7 + 0xf6bb4b60, 16) + d; 154 | b = rotate(b + f3(c, d, a) + word10 + 0xbebfbc70, 23) + c; 155 | 156 | a = rotate(a + f3(b, c, d) + word13 + 0x289b7ec6, 4) + b; 157 | d = rotate(d + f3(a, b, c) + word0 + 0xeaa127fa, 11) + a; 158 | c = rotate(c + f3(d, a, b) + word3 + 0xd4ef3085, 16) + d; 159 | b = rotate(b + f3(c, d, a) + word6 + 0x04881d05, 23) + c; 160 | 161 | a = rotate(a + f3(b, c, d) + word9 + 0xd9d4d039, 4) + b; 162 | d = rotate(d + f3(a, b, c) + word12 + 0xe6db99e5, 11) + a; 163 | c = rotate(c + f3(d, a, b) + word15 + 0x1fa27cf8, 16) + d; 164 | b = rotate(b + f3(c, d, a) + word2 + 0xc4ac5665, 23) + c; 165 | 166 | // fourth round 167 | a = rotate(a + f4(b, c, d) + word0 + 0xf4292244, 6) + b; 168 | d = rotate(d + f4(a, b, c) + word7 + 0x432aff97, 10) + a; 169 | c = rotate(c + f4(d, a, b) + word14 + 0xab9423a7, 15) + d; 170 | b = rotate(b + f4(c, d, a) + word5 + 0xfc93a039, 21) + c; 171 | 172 | a = rotate(a + f4(b, c, d) + word12 + 0x655b59c3, 6) + b; 173 | d = rotate(d + f4(a, b, c) + word3 + 0x8f0ccc92, 10) + a; 174 | c = rotate(c + f4(d, a, b) + word10 + 0xffeff47d, 15) + d; 175 | b = rotate(b + f4(c, d, a) + word1 + 0x85845dd1, 21) + c; 176 | 177 | a = rotate(a + f4(b, c, d) + word8 + 0x6fa87e4f, 6) + b; 178 | d = rotate(d + f4(a, b, c) + word15 + 0xfe2ce6e0, 10) + a; 179 | c = rotate(c + f4(d, a, b) + word6 + 0xa3014314, 15) + d; 180 | b = rotate(b + f4(c, d, a) + word13 + 0x4e0811a1, 21) + c; 181 | 182 | a = rotate(a + f4(b, c, d) + word4 + 0xf7537e82, 6) + b; 183 | d = rotate(d + f4(a, b, c) + word11 + 0xbd3af235, 10) + a; 184 | c = rotate(c + f4(d, a, b) + word2 + 0x2ad7d2bb, 15) + d; 185 | b = rotate(b + f4(c, d, a) + word9 + 0xeb86d391, 21) + c; 186 | 187 | // update hash 188 | m_hash[0] += a; 189 | m_hash[1] += b; 190 | m_hash[2] += c; 191 | m_hash[3] += d; 192 | } 193 | 194 | /// add arbitrary number of bytes 195 | void MD5::add(const void *data, size_t numBytes) { 196 | const uint8_t *current = (const uint8_t *)data; 197 | 198 | if (m_bufferSize > 0) { 199 | while (numBytes > 0 && m_bufferSize < BlockSize) { 200 | m_buffer[m_bufferSize++] = *current++; 201 | numBytes--; 202 | } 203 | } 204 | 205 | // full buffer 206 | if (m_bufferSize == BlockSize) { 207 | processBlock(m_buffer); 208 | m_numBytes += BlockSize; 209 | m_bufferSize = 0; 210 | } 211 | 212 | // no more data ? 213 | if (numBytes == 0) 214 | return; 215 | 216 | // process full blocks 217 | while (numBytes >= BlockSize) { 218 | processBlock(current); 219 | current += BlockSize; 220 | m_numBytes += BlockSize; 221 | numBytes -= BlockSize; 222 | } 223 | 224 | // keep remaining bytes in buffer 225 | while (numBytes > 0) { 226 | m_buffer[m_bufferSize++] = *current++; 227 | numBytes--; 228 | } 229 | } 230 | 231 | /// process final block, less than 64 bytes 232 | void MD5::processBuffer() { 233 | // the input bytes are considered as bits strings, where the first bit is the most significant bit of the byte 234 | 235 | // - append "1" bit to message 236 | // - append "0" bits until message length in bit mod 512 is 448 237 | // - append length as 64 bit integer 238 | 239 | // number of bits 240 | size_t paddedLength = m_bufferSize * 8; 241 | 242 | // plus one bit set to 1 (always appended) 243 | paddedLength++; 244 | 245 | // number of bits must be (numBits % 512) = 448 246 | size_t lower11Bits = paddedLength & 511; 247 | if (lower11Bits <= 448) 248 | paddedLength += 448 - lower11Bits; 249 | else 250 | paddedLength += 512 + 448 - lower11Bits; 251 | // convert from bits to bytes 252 | paddedLength /= 8; 253 | 254 | // only needed if additional data flows over into a second block 255 | unsigned char extra[BlockSize]; 256 | 257 | // append a "1" bit, 128 => binary 10000000 258 | if (m_bufferSize < BlockSize) 259 | m_buffer[m_bufferSize] = 128; 260 | else 261 | extra[0] = 128; 262 | 263 | size_t i; 264 | for (i = m_bufferSize + 1; i < BlockSize; i++) 265 | m_buffer[i] = 0; 266 | for (; i < paddedLength; i++) 267 | extra[i - BlockSize] = 0; 268 | 269 | // add message length in bits as 64 bit number 270 | uint64_t msgBits = 8 * (m_numBytes + m_bufferSize); 271 | // find right position 272 | unsigned char *addLength; 273 | if (paddedLength < BlockSize) 274 | addLength = m_buffer + paddedLength; 275 | else 276 | addLength = extra + paddedLength - BlockSize; 277 | 278 | // must be little endian 279 | *addLength++ = msgBits & 0xFF; 280 | msgBits >>= 8; 281 | *addLength++ = msgBits & 0xFF; 282 | msgBits >>= 8; 283 | *addLength++ = msgBits & 0xFF; 284 | msgBits >>= 8; 285 | *addLength++ = msgBits & 0xFF; 286 | msgBits >>= 8; 287 | *addLength++ = msgBits & 0xFF; 288 | msgBits >>= 8; 289 | *addLength++ = msgBits & 0xFF; 290 | msgBits >>= 8; 291 | *addLength++ = msgBits & 0xFF; 292 | msgBits >>= 8; 293 | *addLength++ = msgBits & 0xFF; 294 | 295 | // process blocks 296 | processBlock(m_buffer); 297 | // flowed over into a second block ? 298 | if (paddedLength > BlockSize) 299 | processBlock(extra); 300 | } 301 | 302 | /// return latest hash as 32 hex characters 303 | std::string MD5::getHash() { 304 | // compute hash (as raw bytes) 305 | unsigned char rawHash[HashBytes]; 306 | getHash(rawHash); 307 | 308 | // convert to hex string 309 | std::string result; 310 | result.reserve(2 * HashBytes); 311 | for (int i = 0; i < HashBytes; i++) { 312 | static const char dec2hex[16 + 1] = "0123456789abcdef"; 313 | result += dec2hex[(rawHash[i] >> 4) & 15]; 314 | result += dec2hex[rawHash[i] & 15]; 315 | } 316 | 317 | return result; 318 | } 319 | 320 | /// return latest hash as bytes 321 | void MD5::getHash(unsigned char buffer[MD5::HashBytes]) { 322 | // save old hash if buffer is partially filled 323 | uint32_t oldHash[HashValues]; 324 | for (int i = 0; i < HashValues; i++) 325 | oldHash[i] = m_hash[i]; 326 | 327 | // process remaining bytes 328 | processBuffer(); 329 | 330 | unsigned char *current = buffer; 331 | for (int i = 0; i < HashValues; i++) { 332 | *current++ = m_hash[i] & 0xFF; 333 | *current++ = (m_hash[i] >> 8) & 0xFF; 334 | *current++ = (m_hash[i] >> 16) & 0xFF; 335 | *current++ = (m_hash[i] >> 24) & 0xFF; 336 | 337 | // restore old hash 338 | m_hash[i] = oldHash[i]; 339 | } 340 | } 341 | 342 | /// compute MD5 of a memory block 343 | std::string MD5::operator()(const void *data, size_t numBytes) { 344 | reset(); 345 | add(data, numBytes); 346 | return getHash(); 347 | } 348 | 349 | /// compute MD5 of a string, excluding final zero 350 | std::string MD5::operator()(const std::string &text) { 351 | reset(); 352 | add(text.c_str(), text.size()); 353 | return getHash(); 354 | } 355 | -------------------------------------------------------------------------------- /src/npbind.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021-2022 Al Azif 2 | // License: GPLv3 3 | 4 | #include "npbind.h" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "common.h" 16 | 17 | #include 18 | 19 | namespace npbind { 20 | std::vector read(const std::string &path) { // Flawfinder: ignore 21 | // Check for empty or pure whitespace path 22 | if (path.empty() || std::all_of(path.begin(), path.end(), [](char c) { return std::isspace(c); })) { 23 | FATAL_ERROR("Empty path argument!"); 24 | } 25 | 26 | // Check if file exists and is file 27 | if (!std::filesystem::is_regular_file(path)) { 28 | FATAL_ERROR("Input path does not exist or is not a file!"); 29 | } 30 | 31 | // Open path 32 | std::ifstream npbind_input(path, std::ios::in | std::ios::binary); 33 | if (!npbind_input || !npbind_input.good()) { 34 | npbind_input.close(); 35 | FATAL_ERROR("Cannot open file: " + std::string(path)); 36 | } 37 | 38 | // Check file magic (Read in whole header) 39 | NpBindHeader header; 40 | npbind_input.read(reinterpret_cast(&header), sizeof(header)); // Flawfinder: ignore 41 | if (!npbind_input.good()) { 42 | npbind_input.close(); 43 | FATAL_ERROR("Error reading header!"); 44 | } 45 | if (__builtin_bswap32(header.magic) != NPBIND_MAGIC) { 46 | npbind_input.close(); 47 | // #include 48 | // std::stringstream ss; 49 | // ss << "File magic does not match npbind.dat! Expected: 0x" << std::uppercase << std::setfill('0') << std::setw(8) << std::hex << NPBIND_MAGIC << " | Actual: 0x" << std::uppercase << std::setfill('0') << std::setw(8) << std::hex << __builtin_bswap32(header.magic); 50 | // FATAL_ERROR(ss.str()); 51 | FATAL_ERROR("Input path is not a npbind.dat!"); 52 | } 53 | 54 | // Read in body(s) 55 | std::vector entries; 56 | for (uint64_t i = 0; i < __builtin_bswap64(header.num_entries); i++) { 57 | NpBindEntry temp_entry; 58 | npbind_input.read(reinterpret_cast(&temp_entry), __builtin_bswap64(header.entry_size)); // Flawfinder: ignore 59 | if (!npbind_input.good()) { 60 | npbind_input.close(); 61 | FATAL_ERROR("Error reading entries!"); 62 | } 63 | entries.push_back(temp_entry); 64 | } 65 | 66 | // Read digest 67 | std::vector digest(SHA1::HashBytes); 68 | npbind_input.seekg(-digest.size(), npbind_input.end); // Make sure we are in the right place 69 | npbind_input.read(reinterpret_cast(&digest[0]), digest.size()); // Flawfinder: ignore 70 | if (!npbind_input.good()) { 71 | // Should never reach here... will affect coverage % 72 | npbind_input.close(); 73 | FATAL_ERROR("Error reading digest!"); 74 | } 75 | npbind_input.close(); 76 | 77 | // Check digest 78 | std::stringstream ss; 79 | ss.write(reinterpret_cast(&header), sizeof(header)); 80 | 81 | for (uint64_t i = 0; i < __builtin_bswap64(header.num_entries); i++) { 82 | ss.write(reinterpret_cast(&entries[i]), sizeof(NpBindEntry)); 83 | } 84 | 85 | std::vector data_to_hash; 86 | for (size_t i = 0; i < ss.str().size(); i++) { 87 | data_to_hash.push_back(ss.str().c_str()[i]); 88 | } 89 | 90 | std::vector calculated_digest(digest.size()); 91 | 92 | SHA1 sha1; 93 | sha1(&data_to_hash[0], ss.str().size()); 94 | sha1.getHash(&calculated_digest[0]); 95 | 96 | if (std::memcmp(&calculated_digest[0], &digest[0], digest.size()) != 0) { 97 | FATAL_ERROR("Digests do not match! Aborting..."); 98 | } 99 | 100 | return entries; 101 | } 102 | } // namespace npbind 103 | -------------------------------------------------------------------------------- /src/pfs.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021-2022 Al Azif 2 | // License: GPLv3 3 | 4 | #include "pfs.h" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include "common.h" 17 | 18 | namespace pfs { 19 | pfs_header header; 20 | size_t pfs_size; 21 | size_t pfs_copied; 22 | std::vector inodes; 23 | 24 | std::ifstream pfs_input; 25 | 26 | void __parse_directory(uint32_t ino, uint32_t level, const std::string &output_path, bool calculate_only) { 27 | for (uint32_t i = 0; i < inodes[ino].blocks; i++) { 28 | uint32_t db = inodes[ino].db[0] + i; 29 | uint64_t pos = static_cast(header.blocksz) * db; 30 | uint64_t size = inodes[ino].size; 31 | uint64_t top = pos + size; 32 | 33 | while (pos < top) { 34 | dirent_t ent; 35 | 36 | pfs_input.seekg(pos, pfs_input.beg); 37 | pfs_input.read(reinterpret_cast(&ent), sizeof(ent)); // Flawfinder: ignore 38 | if (!pfs_input.good()) { 39 | pfs_input.close(); 40 | FATAL_ERROR("Error reading entry!"); 41 | } 42 | 43 | if (ent.type == 0) { 44 | break; 45 | } 46 | 47 | std::vector name(ent.namelen); 48 | if (level > 0) { 49 | pfs_input.seekg(pos + sizeof(dirent_t), pfs_input.beg); 50 | pfs_input.read(reinterpret_cast(&name[0]), name.size()); // Flawfinder: ignore 51 | if (!pfs_input.good()) { 52 | pfs_input.close(); 53 | FATAL_ERROR("Error reading entry name!"); 54 | } 55 | } 56 | 57 | std::filesystem::path new_output_path(output_path); 58 | new_output_path /= std::string(name.begin(), name.end()); 59 | 60 | if (ent.type == 2 && level > 0) { 61 | if (calculate_only) { 62 | pfs_size += inodes[ent.ino].size; 63 | } else { 64 | // Open path 65 | std::ofstream output_file(new_output_path, std::ios::out | std::ios::trunc | std::ios::binary); 66 | if (!output_file || !output_file.good()) { 67 | pfs_input.close(); 68 | output_file.close(); 69 | FATAL_ERROR("Cannot open file: " + std::string(new_output_path)); 70 | } 71 | 72 | unsigned char buffer[PFS_DUMP_BUFFER]; 73 | std::memset(buffer, '\0', sizeof(buffer)); 74 | 75 | std::stringstream ss; 76 | uint64_t dump_counter = 0; 77 | 78 | pfs_input.seekg(static_cast(header.blocksz) * inodes[ent.ino].db[0], pfs_input.beg); 79 | 80 | while (dump_counter + sizeof(buffer) <= inodes[ent.ino].size) { 81 | pfs_input.read(reinterpret_cast(&buffer), sizeof(buffer)); // Flawfinder: ignore 82 | if (!pfs_input.good()) { 83 | pfs_input.close(); 84 | output_file.close(); 85 | FATAL_ERROR("Error reading entry data!"); 86 | } 87 | ss.write(reinterpret_cast(&buffer), sizeof(buffer)); 88 | output_file << ss.rdbuf(); 89 | ss.str(std::string()); 90 | ss.clear(); 91 | std::memset(buffer, '\0', sizeof(buffer)); 92 | pfs_copied += sizeof(buffer); 93 | dump_counter += sizeof(buffer); 94 | } 95 | 96 | pfs_input.read(reinterpret_cast(&buffer), inodes[ent.ino].size - dump_counter); // Flawfinder: ignore 97 | if (!pfs_input.good()) { 98 | pfs_input.close(); 99 | output_file.close(); 100 | FATAL_ERROR("Error reading entry data!"); 101 | } 102 | ss.write(reinterpret_cast(&buffer), inodes[ent.ino].size - dump_counter); 103 | output_file << ss.rdbuf(); 104 | ss.str(std::string()); 105 | ss.clear(); 106 | dump_counter += inodes[ent.ino].size - dump_counter; 107 | pfs_copied += inodes[ent.ino].size - dump_counter; 108 | 109 | output_file.close(); 110 | } 111 | } else if (ent.type == 3) { 112 | if (!calculate_only) { 113 | if (!std::filesystem::is_directory(new_output_path) && !std::filesystem::create_directory(new_output_path)) { 114 | pfs_input.close(); 115 | FATAL_ERROR("Could not create output directory"); 116 | } 117 | } 118 | __parse_directory(ent.ino, level + 1, new_output_path, calculate_only); 119 | } 120 | 121 | pos += ent.entsize; 122 | } 123 | } 124 | } 125 | 126 | void calculate_pfs_size(uint32_t ino, uint32_t level) { 127 | __parse_directory(ino, level, "", true); 128 | } 129 | 130 | void dump_pfs(uint32_t ino, uint32_t level, const std::string &output_path) { 131 | __parse_directory(ino, level, output_path, false); 132 | } 133 | 134 | void extract(const std::string &pfs_path, const std::string &output_path) { 135 | // Make sure output directory path exists or can be created 136 | if (!std::filesystem::is_directory(output_path) && !std::filesystem::create_directories(output_path)) { 137 | pfs_input.close(); 138 | FATAL_ERROR("Unable to open/create output directory"); 139 | } 140 | 141 | // Check for empty or pure whitespace path 142 | if (pfs_path.empty() || std::all_of(pfs_path.begin(), pfs_path.end(), [](char c) { return std::isspace(c); })) { 143 | pfs_input.close(); 144 | FATAL_ERROR("Empty input path argument!"); 145 | } 146 | 147 | // Check if file exists and is file 148 | if (!std::filesystem::is_regular_file(pfs_path)) { 149 | pfs_input.close(); 150 | FATAL_ERROR("Input path does not exist or is not a file!"); 151 | } 152 | 153 | // Open path 154 | pfs_input.open(pfs_path, std::ios::in | std::ios::binary); 155 | if (!pfs_input || !pfs_input.good()) { 156 | pfs_input.close(); 157 | FATAL_ERROR("Cannot open file: " + std::string(pfs_path)); 158 | } 159 | 160 | // Check file magic (Read in whole header) 161 | pfs_input.read(reinterpret_cast(&header), sizeof(header)); // Flawfinder: ignore 162 | if (!pfs_input.good()) { 163 | pfs_input.close(); 164 | FATAL_ERROR("Error reading header!"); 165 | } 166 | if (__builtin_bswap64(header.magic) != PFS_MAGIC) { 167 | pfs_input.close(); 168 | std::stringstream ss; 169 | ss << "File magic does not match pfs_image.dat! Expected: 0x" << std::uppercase << std::setfill('0') << std::setw(16) << std::hex << PFS_MAGIC << " | Actual: 0x" << std::uppercase << std::setfill('0') << std::setw(16) << std::hex << __builtin_bswap64(header.magic); 170 | FATAL_ERROR(ss.str()); 171 | } 172 | 173 | // Read in inodes 174 | for (uint64_t i = 0; i < header.ndinodeblock; i++) { 175 | for (uint64_t j = 0; (j < (header.blocksz / sizeof(di_d32))) && (j < header.ndinode); j++) { 176 | di_d32 temp_inodes; 177 | pfs_input.seekg(static_cast(header.blocksz) * (i + 1) + sizeof(di_d32) * j, pfs_input.beg); 178 | pfs_input.read(reinterpret_cast(&temp_inodes), sizeof(di_d32)); // Flawfinder: ignore 179 | if (!pfs_input.good()) { 180 | pfs_input.close(); 181 | FATAL_ERROR("Error reading inodes!"); 182 | } 183 | inodes.push_back(temp_inodes); 184 | } 185 | } 186 | 187 | pfs_size = 0; 188 | pfs_copied = 0; 189 | 190 | calculate_pfs_size(header.superroot_ino, 0); 191 | dump_pfs(header.superroot_ino, 0, output_path); 192 | 193 | pfs_input.close(); 194 | } 195 | } // namespace pfs 196 | -------------------------------------------------------------------------------- /src/pkg.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021-2022 Al Azif 2 | // License: GPLv3 3 | 4 | #include "pkg.h" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "common.h" 15 | 16 | namespace pkg { 17 | bool is_pkg(const std::string &path) { 18 | // TODO 19 | 20 | UNUSED(path); 21 | 22 | return true; 23 | } 24 | 25 | bool is_fpkg(const std::string &path) { 26 | // TODO 27 | 28 | UNUSED(path); 29 | 30 | return true; 31 | } 32 | 33 | std::string get_entry_name_by_type(uint32_t type) { 34 | std::stringstream ss; 35 | 36 | if (type == 0x0001) { 37 | ss << ".digests"; 38 | } else if (type == 0x0010) { 39 | ss << ".entry_keys"; 40 | } else if (type == 0x0020) { 41 | ss << ".image_key"; 42 | } else if (type == 0x0021) { 43 | ss << ".unknown_0x21"; 44 | } else if (type == 0x0080) { 45 | ss << ".general_digests"; 46 | } else if (type == 0x00C0) { 47 | ss << ".unknown_0xC0"; 48 | } else if (type == 0x0100) { 49 | ss << ".metas"; 50 | } else if (type == 0x0200) { 51 | ss << ".entry_names"; 52 | } else if (type == 0x0400) { 53 | ss << "license.dat"; 54 | } else if (type == 0x0401) { 55 | ss << "license.info"; 56 | } else if (type == 0x0402) { 57 | ss << "nptitle.dat"; 58 | } else if (type == 0x0403) { 59 | ss << "npbind.dat"; 60 | } else if (type == 0x0404) { 61 | ss << "selfinfo.dat"; 62 | } else if (type == 0x0406) { 63 | ss << "imageinfo.dat"; 64 | } else if (type == 0x0407) { 65 | ss << "target-deltainfo.dat"; 66 | } else if (type == 0x0408) { 67 | ss << "origin-deltainfo.dat"; 68 | } else if (type == 0x0409) { 69 | ss << "psreserved.dat"; 70 | } else if (type == 0x1000) { 71 | ss << "param.sfo"; 72 | } else if (type == 0x1001) { 73 | ss << "playgo-chunk.dat"; 74 | } else if (type == 0x1002) { 75 | ss << "playgo-chunk.sha"; 76 | } else if (type == 0x1003) { 77 | ss << "playgo-manifest.xml"; 78 | } else if (type == 0x1004) { 79 | ss << "pronunciation.xml"; 80 | } else if (type == 0x1005) { 81 | ss << "pronunciation.sig"; 82 | } else if (type == 0x1006) { 83 | ss << "pic1.png"; 84 | } else if (type == 0x1007) { 85 | ss << "pubtoolinfo.dat"; 86 | } else if (type == 0x1008) { 87 | ss << "app/playgo-chunk.dat"; 88 | } else if (type == 0x1009) { 89 | ss << "app/playgo-chunk.sha"; 90 | } else if (type == 0x100A) { 91 | ss << "app/playgo-manifest.xml"; 92 | } else if (type == 0x100B) { 93 | ss << "shareparam.json"; 94 | } else if (type == 0x100C) { 95 | ss << "shareoverlayimage.png"; 96 | } else if (type == 0x100D) { 97 | ss << "save_data.png"; 98 | } else if (type == 0x100E) { 99 | ss << "shareprivacyguardimage.png"; 100 | } else if (type == 0x1200) { 101 | ss << "icon0.png"; 102 | } else if ((type >= 0x1201) && (type <= 0x121F)) { 103 | ss << "icon0_" << std::setfill('0') << std::setw(2) << type - 0x1201 << ".png"; 104 | } else if (type == 0x1220) { 105 | ss << "pic0.png"; 106 | } else if (type == 0x1240) { 107 | ss << "snd0.at9"; 108 | } else if ((type >= 0x1241) && (type <= 0x125F)) { 109 | ss << "pic1_" << std::setfill('0') << std::setw(2) << type - 0x1241 << ".png"; 110 | } else if (type == 0x1260) { 111 | ss << "changeinfo/changeinfo.xml"; 112 | } else if ((type >= 0x1261) && (type <= 0x127F)) { 113 | ss << "changeinfo/changeinfo_" << std::setfill('0') << std::setw(2) << type - 0x1261 << ".xml"; 114 | } else if (type == 0x1280) { 115 | ss << "icon0.dds"; 116 | } else if ((type >= 0x1281) && (type <= 0x129F)) { 117 | ss << "icon0_" << std::setfill('0') << std::setw(2) << type - 0x1281 << ".dds"; 118 | } else if (type == 0x12A0) { 119 | ss << "pic0.dds"; 120 | } else if (type == 0x12C0) { 121 | ss << "pic1.dds"; 122 | } else if ((type >= 0x12C1) && (type <= 0x12DF)) { 123 | ss << "pic1_" << std::setfill('0') << std::setw(2) << type - 0x12C1 << ".dds"; 124 | } else if ((type >= 0x1400) && (type <= 0x147F)) { 125 | ss << "trophy/trophy" << std::setfill('0') << std::setw(2) << type - 0x1400 << ".trp"; 126 | } else if ((type >= 0x1600) && (type <= 0x1609)) { 127 | ss << "keymap_rp/" << std::setfill('0') << std::setw(3) << type - 0x15FF << ".png"; 128 | } else if ((type >= 0x1610) && (type <= 0x16F5)) { 129 | ss << "keymap_rp/" << std::setfill('0') << std::setw(2) << (type - 0x1610) / 10 << "/" << std::setfill('0') << std::setw(3) << (((type - 0x160F) % 10) ? (type - 0x160F) % 10 : 10) << ".png"; 130 | } 131 | 132 | return ss.str(); 133 | } 134 | 135 | void extract_sc0(const std::string &pkg_path, const std::string &output_path) { 136 | // Check for empty or pure whitespace path 137 | if (pkg_path.empty() || std::all_of(pkg_path.begin(), pkg_path.end(), [](char c) { return std::isspace(c); })) { 138 | FATAL_ERROR("Empty input path argument!"); 139 | } 140 | 141 | // Check if file exists and is file 142 | if (!std::filesystem::is_regular_file(pkg_path)) { 143 | FATAL_ERROR("Input path does not exist or is not a file!"); 144 | } 145 | 146 | // Open path 147 | std::ifstream pkg_input(pkg_path, std::ios::in | std::ios::binary); 148 | if (!pkg_input || !pkg_input.good()) { 149 | pkg_input.close(); 150 | FATAL_ERROR("Cannot open input file: " + std::string(pkg_path)); 151 | } 152 | 153 | // Check file magic (Read in whole header) 154 | PkgHeader header; 155 | pkg_input.read(reinterpret_cast(&header), sizeof(header)); // Flawfinder: ignore 156 | if (!pkg_input.good()) { 157 | pkg_input.close(); 158 | FATAL_ERROR("Error reading PKG header!"); 159 | } 160 | if (__builtin_bswap32(header.magic) != PKG_MAGIC) { 161 | pkg_input.close(); 162 | // #include 163 | // std::stringstream ss; 164 | // ss << "File magic does not match a PKG! Expected: 0x" << std::uppercase << std::setfill('0') << std::setw(8) << std::hex << PKG_MAGIC << " | Actual: 0x" << std::uppercase << std::setfill('0') << std::setw(8) << std::hex << __builtin_bswap32(header.magic); 165 | // FATAL_ERROR(ss.str()); 166 | FATAL_ERROR("Input path is not a PKG!"); 167 | } 168 | 169 | // Read PKG entry table entries 170 | std::vector entries; 171 | pkg_input.seekg(__builtin_bswap32(header.entry_table_offset), pkg_input.beg); 172 | for (uint32_t i = 0; i < __builtin_bswap32(header.entry_count); i++) { 173 | PkgTableEntry temp_entry; 174 | pkg_input.read(reinterpret_cast(&temp_entry), sizeof(temp_entry)); // Flawfinder: ignore 175 | if (!pkg_input.good()) { 176 | pkg_input.close(); 177 | FATAL_ERROR("Error reading entry table!"); 178 | } 179 | entries.push_back(temp_entry); 180 | } 181 | 182 | // Check for empty or pure whitespace path 183 | if (output_path.empty() || std::all_of(output_path.begin(), output_path.end(), [](char c) { return std::isspace(c); })) { 184 | FATAL_ERROR("Empty output path argument!"); 185 | } 186 | 187 | // Make sure output directory path exists or can be created 188 | if (!std::filesystem::is_directory(output_path) && !std::filesystem::create_directories(output_path)) { 189 | pkg_input.close(); 190 | FATAL_ERROR("Unable to open/create output directory"); 191 | } 192 | 193 | // Extract sc0 entries 194 | for (auto &&entry : entries) { 195 | std::string entry_name = get_entry_name_by_type(__builtin_bswap32(entry.id)); 196 | if (!entry_name.empty()) { 197 | bool entry_encrpyted((__builtin_bswap32(entry.flags1) & 0x80000000) != 0); 198 | uint32_t entry_key_index = (__builtin_bswap32(entry.flags2) & 0xF000) >> 12; 199 | 200 | std::filesystem::path temp_output_path(output_path); 201 | temp_output_path /= entry_name; 202 | 203 | pkg_input.seekg(__builtin_bswap32(entry.offset), pkg_input.beg); 204 | std::vector temp_file(__builtin_bswap32(entry.size)); 205 | pkg_input.read(reinterpret_cast(&temp_file[0]), temp_file.size()); // Flawfinder: ignore 206 | if (!pkg_input.good()) { 207 | pkg_input.close(); 208 | FATAL_ERROR("Error reading entry data!"); 209 | } 210 | 211 | std::filesystem::path temp_output_dir = temp_output_path; 212 | temp_output_dir.remove_filename(); 213 | 214 | if (!std::filesystem::is_directory(temp_output_dir) && !std::filesystem::create_directories(temp_output_dir)) { 215 | pkg_input.close(); 216 | FATAL_ERROR("Unable to open/create output subdirectory"); 217 | } 218 | 219 | if (entry_encrpyted) { 220 | // Only have key at index 3 221 | if (entry_key_index == 3) { 222 | // TODO: Decrypt and save as `temp_output_path` 223 | } 224 | temp_output_path += ".encrypted"; 225 | } 226 | 227 | // Open path 228 | std::ofstream output_file(temp_output_path, std::ios::out | std::ios::trunc | std::ios::binary); 229 | if (!output_file || !output_file.good()) { 230 | pkg_input.close(); 231 | output_file.close(); 232 | FATAL_ERROR("Cannot open file: " + std::string(temp_output_path)); 233 | } 234 | 235 | // Write to file 236 | std::stringstream ss; 237 | ss.write(reinterpret_cast(&temp_file[0]), temp_file.size()); 238 | output_file << ss.rdbuf(); 239 | output_file.close(); 240 | } 241 | } 242 | pkg_input.close(); 243 | } 244 | } // namespace pkg 245 | -------------------------------------------------------------------------------- /src/sfo.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021-2022 Al Azif 2 | // License: GPLv3 3 | 4 | #include "sfo.h" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "common.h" 15 | 16 | namespace sfo { 17 | bool is_sfo(const std::string &path) { 18 | // Check for empty or pure whitespace path 19 | if (path.empty() || std::all_of(path.begin(), path.end(), [](char c) { return std::isspace(c); })) { 20 | FATAL_ERROR("Empty path argument!"); 21 | } 22 | 23 | // Check if file exists and is file 24 | if (!std::filesystem::is_regular_file(path)) { 25 | FATAL_ERROR("Input path does not exist or is not a file!"); 26 | } 27 | 28 | // Open path 29 | std::ifstream sfo_input(path, std::ios::in | std::ios::binary); 30 | if (!sfo_input || !sfo_input.good()) { 31 | sfo_input.close(); 32 | FATAL_ERROR("Cannot open file: " + std::string(path)); 33 | } 34 | 35 | // Read SFO header 36 | SfoHeader header; 37 | sfo_input.read(reinterpret_cast(&header), sizeof(header)); // Flawfinder: ignore 38 | if (!sfo_input.good()) { 39 | sfo_input.close(); 40 | return false; 41 | } 42 | sfo_input.close(); 43 | 44 | // Compare magic 45 | if (__builtin_bswap32(header.magic) == SFO_MAGIC) { 46 | return true; 47 | } 48 | 49 | return false; 50 | } 51 | 52 | std::vector read(const std::string &path) { // Flawfinder: ignore 53 | // Check for empty or pure whitespace path 54 | if (path.empty() || std::all_of(path.begin(), path.end(), [](char c) { return std::isspace(c); })) { 55 | FATAL_ERROR("Empty path argument!"); 56 | } 57 | 58 | // Check if file exists and is file 59 | if (!std::filesystem::is_regular_file(path)) { 60 | FATAL_ERROR("Input path does not exist or is not a file!"); 61 | } 62 | 63 | // Open path 64 | std::ifstream sfo_input(path, std::ios::in | std::ios::binary); 65 | if (!sfo_input || !sfo_input.good()) { 66 | sfo_input.close(); 67 | FATAL_ERROR("Cannot open file: " + std::string(path)); 68 | } 69 | 70 | // Check to make sure file is a SFO 71 | if (!is_sfo(path)) { 72 | sfo_input.close(); 73 | FATAL_ERROR("Input path is not a SFO!"); 74 | } 75 | 76 | // Read SFO header 77 | SfoHeader header; 78 | sfo_input.read(reinterpret_cast(&header), sizeof(header)); // Flawfinder: ignore 79 | if (!sfo_input.good()) { 80 | // Should never reach here... will affect coverage % 81 | sfo_input.close(); 82 | FATAL_ERROR("Error reading SFO header!"); 83 | } 84 | 85 | std::vector data; 86 | 87 | // Read data 88 | for (size_t i = 0; i < header.num_entries; i++) { 89 | // Cannot just read sizeof(SfoData) as it includes a std::string value for key_name which is added in the next loop along with the actual data 90 | SfoData temp_data; 91 | sfo_input.read(reinterpret_cast(&temp_data.key_offset), sizeof(temp_data.key_offset)); // Flawfinder: ignore 92 | if (!sfo_input.good()) { 93 | sfo_input.close(); 94 | FATAL_ERROR("Error reading entry key offset!"); 95 | } 96 | sfo_input.read(reinterpret_cast(&temp_data.format), sizeof(temp_data.format)); // Flawfinder: ignore 97 | if (!sfo_input.good()) { 98 | sfo_input.close(); 99 | FATAL_ERROR("Error reading entry format!"); 100 | } 101 | sfo_input.read(reinterpret_cast(&temp_data.length), sizeof(temp_data.length)); // Flawfinder: ignore 102 | if (!sfo_input.good()) { 103 | sfo_input.close(); 104 | FATAL_ERROR("Error reading entry length!"); 105 | } 106 | sfo_input.read(reinterpret_cast(&temp_data.max_length), sizeof(temp_data.max_length)); // Flawfinder: ignore 107 | if (!sfo_input.good()) { 108 | sfo_input.close(); 109 | FATAL_ERROR("Error reading entry max length!"); 110 | } 111 | sfo_input.read(reinterpret_cast(&temp_data.data_offset), sizeof(temp_data.data_offset)); // Flawfinder: ignore 112 | if (!sfo_input.good()) { 113 | sfo_input.close(); 114 | FATAL_ERROR("Error reading entry data offset!"); 115 | } 116 | data.push_back(temp_data); 117 | } 118 | 119 | // Read key names and data 120 | for (auto &&entry : data) { 121 | sfo_input.seekg(header.key_table_offset + entry.key_offset, sfo_input.beg); 122 | std::getline(sfo_input, entry.key_name, '\0'); 123 | 124 | sfo_input.seekg(header.data_table_offset + entry.data_offset, sfo_input.beg); 125 | sfo_input.read(reinterpret_cast(&entry.data[0]), entry.length); // Flawfinder: ignore 126 | if (!sfo_input.good()) { 127 | sfo_input.close(); 128 | FATAL_ERROR("Error reading data table!"); 129 | } 130 | } 131 | sfo_input.close(); 132 | 133 | return data; 134 | } 135 | 136 | std::vector get_keys(const std::vector &data) { 137 | std::vector temp_key_list; 138 | for (auto &&entry : data) { 139 | temp_key_list.push_back(entry.key_name); 140 | } 141 | return temp_key_list; 142 | } 143 | 144 | uint16_t get_format(const std::string &key, const std::vector &data) { 145 | for (auto &&entry : data) { 146 | if (entry.key_name == key) { 147 | return entry.format; 148 | } 149 | } 150 | FATAL_ERROR("Could not find key"); 151 | } 152 | 153 | uint32_t get_length(const std::string &key, const std::vector &data) { 154 | for (auto &&entry : data) { 155 | if (entry.key_name == key) { 156 | return entry.length; 157 | } 158 | } 159 | FATAL_ERROR("Could not find key"); 160 | } 161 | 162 | uint32_t get_max_length(const std::string &key, const std::vector &data) { 163 | for (auto &&entry : data) { 164 | if (entry.key_name == key) { 165 | return entry.max_length; 166 | } 167 | } 168 | FATAL_ERROR("Could not find key"); 169 | } 170 | 171 | std::vector get_value(const std::string &key, const std::vector &data) { 172 | for (auto &&entry : data) { 173 | if (entry.key_name == key) { 174 | std::vector buffer; 175 | for (uint32_t i = 0; i < entry.length; i++) { 176 | buffer.push_back(entry.data[i]); 177 | } 178 | return buffer; 179 | } 180 | } 181 | FATAL_ERROR("Could not find key"); 182 | } 183 | 184 | std::vector read_pubtool_data(const std::vector &data) { 185 | std::vector sfo_keys = get_keys(data); 186 | std::vector pubtool_data; 187 | 188 | if (std::count(sfo_keys.begin(), sfo_keys.end(), std::string("PUBTOOLINFO"))) { 189 | std::vector pubtoolinfo_buffer = get_value("PUBTOOLINFO", data); 190 | std::stringstream ss(std::string(pubtoolinfo_buffer.begin(), pubtoolinfo_buffer.end())); 191 | 192 | std::vector csv; 193 | while (ss.good()) { 194 | std::string substr; 195 | getline(ss, substr, ','); 196 | csv.push_back(substr); 197 | } 198 | 199 | for (auto &&value : csv) { 200 | SfoPubtoolinfoIndex temp_index; 201 | temp_index.key_name = value.substr(0, value.find('=')); 202 | temp_index.value = value.substr(value.find('=') + 1, value.size()); 203 | pubtool_data.push_back(temp_index); 204 | } 205 | } 206 | 207 | return pubtool_data; 208 | } 209 | 210 | std::vector get_pubtool_keys(const std::vector &data) { 211 | std::vector temp_key_list; 212 | for (auto &&entry : data) { 213 | temp_key_list.push_back(entry.key_name); 214 | } 215 | return temp_key_list; 216 | } 217 | 218 | std::string get_pubtool_value(const std::string &key, const std::vector &data) { 219 | for (auto &&entry : data) { 220 | if (entry.key_name == key) { 221 | return entry.value; 222 | } 223 | } 224 | FATAL_ERROR("Could not find key"); 225 | } 226 | 227 | SfoData build_data(const std::string &key_name, const std::string &format, uint32_t length, uint32_t max_length, const std::vector &data) { 228 | SfoData new_data; 229 | 230 | new_data.key_name = key_name; 231 | new_data.key_offset = 0; 232 | new_data.data_offset = 0; 233 | 234 | // Calculate format value 235 | if (format == "special") { 236 | // Used in contents generated by the system (e.g.: save data) 237 | new_data.format = 0x0004; 238 | } else if (format == "utf-8") { 239 | // Character string, NULL finished (0x00) 240 | new_data.format = 0x0204; 241 | } else if (format == "integer") { 242 | // 32 bits unsigned 243 | new_data.format = 0x0404; 244 | } else { 245 | FATAL_ERROR("Unknown SFO format type!"); 246 | } 247 | 248 | if (length > max_length) { 249 | FATAL_ERROR("Input `length` of SFO entry must be <= input `max_length`!"); 250 | } 251 | new_data.length = length; 252 | new_data.max_length = max_length; 253 | 254 | if (data.size() > length) { 255 | FATAL_ERROR("Input SFO data is larger than the `length`"); 256 | } 257 | new_data.data = data; 258 | 259 | return new_data; 260 | } 261 | 262 | SfoPubtoolinfoIndex build_pubtool_data(const std::string &key, const std::string &value) { 263 | SfoPubtoolinfoIndex new_data; 264 | new_data.key_name = key; 265 | new_data.value = value; 266 | 267 | return new_data; 268 | } 269 | 270 | std::vector add_data(const SfoData &data_to_add, const std::vector ¤t_data) { 271 | // Validate data_to_add 272 | if (data_to_add.format != 0x0004 && data_to_add.format != 0x0204 && data_to_add.format != 0x0404) { 273 | FATAL_ERROR("Unknown SFO format type!"); 274 | } 275 | if (data_to_add.length > data_to_add.max_length) { 276 | FATAL_ERROR("Input `length` of SFO entry must be <= input `max_length`!"); 277 | } 278 | if (data_to_add.data.size() > data_to_add.length) { 279 | FATAL_ERROR("Input SFO data is larger than the `length`"); 280 | } 281 | 282 | // Remove existing key from current data and add new data 283 | std::vector new_data = remove_key(data_to_add.key_name, current_data); 284 | new_data.push_back(data_to_add); 285 | 286 | return new_data; 287 | } 288 | 289 | std::vector add_pubtool_data(const SfoPubtoolinfoIndex &data_to_add, const std::vector ¤t_data) { 290 | std::vector new_data = remove_pubtool_key(data_to_add.key_name, current_data); 291 | std::vector pubtool_data = read_pubtool_data(new_data); 292 | 293 | std::vector new_value; 294 | for (auto &&entry : pubtool_data) { 295 | for (auto &&character : entry.key_name) { 296 | new_value.push_back(character); 297 | } 298 | new_value.push_back('='); 299 | for (auto &&character : entry.value) { 300 | new_value.push_back(character); 301 | } 302 | new_value.push_back(','); 303 | } 304 | new_value.pop_back(); // Remove trailing comma 305 | if (new_value.size() > 0x200) { 306 | FATAL_ERROR("New PUBTOOLINFO key is too large (> 0x200)!"); 307 | } 308 | 309 | SfoData new_entry = build_data("PUBTOOLINFO", "utf-8", new_value.size(), 0x200, new_value); 310 | new_data = add_data(new_entry, new_data); 311 | 312 | return new_data; 313 | } 314 | 315 | std::vector remove_key(const std::string &remove_key, const std::vector ¤t_data) { 316 | std::vector new_data; 317 | for (auto &&entry : current_data) { 318 | if (entry.key_name != remove_key) { 319 | new_data.push_back(entry); 320 | } 321 | } 322 | 323 | return new_data; 324 | } 325 | 326 | std::vector remove_pubtool_key(const std::string &remove_key, const std::vector ¤t_data) { 327 | std::vector sfo_keys = sfo::get_keys(current_data); 328 | if (!std::count(sfo_keys.begin(), sfo_keys.end(), std::string("PUBTOOLINFO"))) { 329 | return current_data; 330 | } 331 | 332 | std::vector pubtool_data = read_pubtool_data(current_data); 333 | std::vector new_pubtool_data; 334 | 335 | for (auto &&index : pubtool_data) { 336 | if (index.key_name != remove_key) { 337 | new_pubtool_data.push_back(index); 338 | } 339 | } 340 | 341 | std::vector new_value; 342 | for (auto &&entry : new_pubtool_data) { 343 | for (auto &&character : entry.key_name) { 344 | new_value.push_back(character); 345 | } 346 | new_value.push_back('='); 347 | for (auto &&character : entry.value) { 348 | new_value.push_back(character); 349 | } 350 | new_value.push_back(','); 351 | } 352 | new_value.pop_back(); // Remove trailing comma 353 | if (new_value.size() > 0x200) { 354 | FATAL_ERROR("New PUBTOOLINFO key is too large (> 0x200)!"); 355 | } 356 | 357 | SfoData new_entry = build_data("PUBTOOLINFO", "utf-8", new_value.size(), 0x200, new_value); 358 | std::vector new_data = add_data(new_entry, current_data); 359 | 360 | return new_data; 361 | } 362 | 363 | bool compare_sfo_data(SfoData data_1, SfoData data_2) { 364 | return (data_1.key_name.compare(data_2.key_name) < 0); 365 | } 366 | 367 | void write(const std::vector &data, const std::string &path) { 368 | // Check for empty or pure whitespace path 369 | if (path.empty() || std::all_of(path.begin(), path.end(), [](char c) { return std::isspace(c); })) { 370 | FATAL_ERROR("Empty path argument!"); 371 | } 372 | 373 | std::filesystem::path output_path(path); 374 | 375 | // Exists, but is not a file 376 | if (std::filesystem::exists(output_path) && !std::filesystem::is_regular_file(output_path)) { 377 | FATAL_ERROR("Output path exists, but is not a file!"); 378 | } 379 | 380 | // Open path 381 | std::ofstream output_file(output_path, std::ios::out | std::ios::trunc | std::ios::binary); 382 | if (!output_file || !output_file.good()) { 383 | output_file.close(); 384 | FATAL_ERROR("Cannot open output file: " + std::string(output_path)); 385 | } 386 | 387 | // Check for duplicate key_name 388 | for (size_t i = 0; i < data.size(); i++) { 389 | for (size_t j = 0; j < data.size(); j++) { 390 | if (i != j && data[i].key_name == data[j].key_name) { 391 | FATAL_ERROR("Duplicate key name found in SFO!"); 392 | } 393 | } 394 | } 395 | 396 | // Validate/Standardize data 397 | std::vector new_data; 398 | for (auto &&entry : data) { 399 | if (entry.format != 0x0004 && entry.format != 0x0204 && entry.format != 0x0404) { 400 | output_file.close(); 401 | FATAL_ERROR("Unknown SFO format type!"); 402 | } 403 | if (entry.length > entry.max_length) { 404 | output_file.close(); 405 | FATAL_ERROR("Input `length` of SFO entry must be <= input `max_length`!"); 406 | } 407 | if (entry.data.size() > entry.length) { 408 | output_file.close(); 409 | FATAL_ERROR("Input SFO data is larger than the `length`"); 410 | } 411 | 412 | SfoData temp_data = entry; 413 | temp_data.key_offset = 0; 414 | temp_data.data_offset = 0; 415 | 416 | new_data.push_back(temp_data); 417 | } 418 | 419 | // Alphabetize new_data by key_name 420 | std::sort(new_data.begin(), new_data.end(), compare_sfo_data); 421 | 422 | // Calculate file offsets 423 | uint16_t total_key_offset = 0; 424 | uint32_t total_data_offset = 0; 425 | 426 | // Calculate key and data table offsets 427 | for (auto &&entry : new_data) { 428 | entry.key_offset = total_key_offset; 429 | total_key_offset += entry.key_name.size(); 430 | total_key_offset++; // Null terminator 431 | 432 | entry.data_offset = total_data_offset; 433 | total_data_offset += entry.max_length; 434 | } 435 | 436 | // Add alignment bytes 437 | while (total_key_offset % 0x4 != 0) { 438 | total_key_offset++; 439 | } 440 | 441 | // Build header 442 | SfoHeader header; 443 | header.magic = __builtin_bswap32(SFO_MAGIC); 444 | header.version = 0x00000101; 445 | header.key_table_offset = new_data.size() * 0x10 + sizeof(header); 446 | header.data_table_offset = header.key_table_offset + total_key_offset; 447 | header.num_entries = new_data.size(); 448 | 449 | // Create stringstream 450 | std::stringstream ss; 451 | 452 | // Write header to stringstream 453 | ss.write(reinterpret_cast(&header), sizeof(header)); 454 | 455 | // Write index to stringstream 456 | for (auto &&entry : new_data) { 457 | ss.write(reinterpret_cast(&entry.key_offset), sizeof(entry.key_offset)); 458 | ss.write(reinterpret_cast(&entry.format), sizeof(entry.format)); 459 | ss.write(reinterpret_cast(&entry.length), sizeof(entry.length)); 460 | ss.write(reinterpret_cast(&entry.max_length), sizeof(entry.max_length)); 461 | ss.write(reinterpret_cast(&entry.data_offset), sizeof(entry.data_offset)); 462 | } 463 | 464 | // Build key table 465 | std::vector key_table; 466 | for (auto &&entry : new_data) { 467 | for (size_t i = 0; i < entry.key_name.size(); i++) { 468 | key_table.insert(key_table.begin() + entry.key_offset + i, entry.key_name[i]); 469 | if (i == entry.key_name.size() - 1) { 470 | key_table.insert(key_table.begin() + entry.key_offset + i + 1, '\0'); 471 | } 472 | } 473 | } 474 | 475 | // Add alignment bytes to key table 476 | while (key_table.size() % 0x4 != 0) { 477 | key_table.push_back('\0'); 478 | } 479 | 480 | // Write key table to stringstream 481 | for (auto &&entry : key_table) { 482 | ss.write(reinterpret_cast(&entry), sizeof(entry)); 483 | } 484 | 485 | // Build data table 486 | std::vector data_table; 487 | for (auto &&entry : new_data) { 488 | for (size_t i = 0; i < entry.max_length; i++) { 489 | if (i < entry.length) { 490 | data_table.insert(data_table.begin() + entry.data_offset + i, entry.data[i]); 491 | } else { 492 | data_table.insert(data_table.begin() + entry.data_offset + i, '\0'); 493 | } 494 | } 495 | } 496 | 497 | // Write data table to stringstream 498 | for (auto &&entry : data_table) { 499 | ss.write(reinterpret_cast(&entry), sizeof(entry)); 500 | } 501 | 502 | // Write streamstream to output file 503 | output_file << ss.rdbuf(); 504 | output_file.close(); 505 | } 506 | } // namespace sfo 507 | -------------------------------------------------------------------------------- /src/sha1.cpp: -------------------------------------------------------------------------------- 1 | // ////////////////////////////////////////////////////////// 2 | // sha1.cpp 3 | // Copyright (c) 2014,2015 Stephan Brumme. All rights reserved. 4 | // see http://create.stephan-brumme.com/disclaimer.html 5 | // 6 | 7 | #include "sha1.h" 8 | 9 | // big endian architectures need #define __BYTE_ORDER __BIG_ENDIAN 10 | #ifndef _MSC_VER 11 | #include 12 | #endif 13 | 14 | /// same as reset() 15 | SHA1::SHA1() { 16 | reset(); 17 | } 18 | 19 | /// restart 20 | void SHA1::reset() { 21 | m_numBytes = 0; 22 | m_bufferSize = 0; 23 | 24 | // according to RFC 1321 25 | m_hash[0] = 0x67452301; 26 | m_hash[1] = 0xefcdab89; 27 | m_hash[2] = 0x98badcfe; 28 | m_hash[3] = 0x10325476; 29 | m_hash[4] = 0xc3d2e1f0; 30 | } 31 | 32 | namespace { 33 | // mix functions for processBlock() 34 | inline uint32_t f1(uint32_t b, uint32_t c, uint32_t d) { 35 | return d ^ (b & (c ^ d)); // original: f = (b & c) | ((~b) & d); 36 | } 37 | 38 | inline uint32_t f2(uint32_t b, uint32_t c, uint32_t d) { 39 | return b ^ c ^ d; 40 | } 41 | 42 | inline uint32_t f3(uint32_t b, uint32_t c, uint32_t d) { 43 | return (b & c) | (b & d) | (c & d); 44 | } 45 | 46 | inline uint32_t rotate(uint32_t a, uint32_t c) { 47 | return (a << c) | (a >> (32 - c)); 48 | } 49 | 50 | inline uint32_t swap(uint32_t x) { 51 | #if defined(__GNUC__) || defined(__clang__) 52 | return __builtin_bswap32(x); 53 | #endif 54 | #ifdef MSC_VER 55 | return _byteswap_ulong(x); 56 | #endif 57 | 58 | return (x >> 24) | 59 | ((x >> 8) & 0x0000FF00) | 60 | ((x << 8) & 0x00FF0000) | 61 | (x << 24); 62 | } 63 | } // namespace 64 | 65 | /// process 64 bytes 66 | void SHA1::processBlock(const void *data) { 67 | // get last hash 68 | uint32_t a = m_hash[0]; 69 | uint32_t b = m_hash[1]; 70 | uint32_t c = m_hash[2]; 71 | uint32_t d = m_hash[3]; 72 | uint32_t e = m_hash[4]; 73 | 74 | // data represented as 16x 32-bit words 75 | const uint32_t *input = (uint32_t *)data; 76 | // convert to big endian 77 | uint32_t words[80]; 78 | for (int i = 0; i < 16; i++) 79 | #if defined(__BYTE_ORDER) && (__BYTE_ORDER != 0) && (__BYTE_ORDER == __BIG_ENDIAN) 80 | words[i] = input[i]; 81 | #else 82 | words[i] = swap(input[i]); 83 | #endif 84 | 85 | // extend to 80 words 86 | for (int i = 16; i < 80; i++) 87 | words[i] = rotate(words[i - 3] ^ words[i - 8] ^ words[i - 14] ^ words[i - 16], 1); 88 | 89 | // first round 90 | for (int i = 0; i < 4; i++) { 91 | int offset = 5 * i; 92 | e += rotate(a, 5) + f1(b, c, d) + words[offset] + 0x5a827999; 93 | b = rotate(b, 30); 94 | d += rotate(e, 5) + f1(a, b, c) + words[offset + 1] + 0x5a827999; 95 | a = rotate(a, 30); 96 | c += rotate(d, 5) + f1(e, a, b) + words[offset + 2] + 0x5a827999; 97 | e = rotate(e, 30); 98 | b += rotate(c, 5) + f1(d, e, a) + words[offset + 3] + 0x5a827999; 99 | d = rotate(d, 30); 100 | a += rotate(b, 5) + f1(c, d, e) + words[offset + 4] + 0x5a827999; 101 | c = rotate(c, 30); 102 | } 103 | 104 | // second round 105 | for (int i = 4; i < 8; i++) { 106 | int offset = 5 * i; 107 | e += rotate(a, 5) + f2(b, c, d) + words[offset] + 0x6ed9eba1; 108 | b = rotate(b, 30); 109 | d += rotate(e, 5) + f2(a, b, c) + words[offset + 1] + 0x6ed9eba1; 110 | a = rotate(a, 30); 111 | c += rotate(d, 5) + f2(e, a, b) + words[offset + 2] + 0x6ed9eba1; 112 | e = rotate(e, 30); 113 | b += rotate(c, 5) + f2(d, e, a) + words[offset + 3] + 0x6ed9eba1; 114 | d = rotate(d, 30); 115 | a += rotate(b, 5) + f2(c, d, e) + words[offset + 4] + 0x6ed9eba1; 116 | c = rotate(c, 30); 117 | } 118 | 119 | // third round 120 | for (int i = 8; i < 12; i++) { 121 | int offset = 5 * i; 122 | e += rotate(a, 5) + f3(b, c, d) + words[offset] + 0x8f1bbcdc; 123 | b = rotate(b, 30); 124 | d += rotate(e, 5) + f3(a, b, c) + words[offset + 1] + 0x8f1bbcdc; 125 | a = rotate(a, 30); 126 | c += rotate(d, 5) + f3(e, a, b) + words[offset + 2] + 0x8f1bbcdc; 127 | e = rotate(e, 30); 128 | b += rotate(c, 5) + f3(d, e, a) + words[offset + 3] + 0x8f1bbcdc; 129 | d = rotate(d, 30); 130 | a += rotate(b, 5) + f3(c, d, e) + words[offset + 4] + 0x8f1bbcdc; 131 | c = rotate(c, 30); 132 | } 133 | 134 | // fourth round 135 | for (int i = 12; i < 16; i++) { 136 | int offset = 5 * i; 137 | e += rotate(a, 5) + f2(b, c, d) + words[offset] + 0xca62c1d6; 138 | b = rotate(b, 30); 139 | d += rotate(e, 5) + f2(a, b, c) + words[offset + 1] + 0xca62c1d6; 140 | a = rotate(a, 30); 141 | c += rotate(d, 5) + f2(e, a, b) + words[offset + 2] + 0xca62c1d6; 142 | e = rotate(e, 30); 143 | b += rotate(c, 5) + f2(d, e, a) + words[offset + 3] + 0xca62c1d6; 144 | d = rotate(d, 30); 145 | a += rotate(b, 5) + f2(c, d, e) + words[offset + 4] + 0xca62c1d6; 146 | c = rotate(c, 30); 147 | } 148 | 149 | // update hash 150 | m_hash[0] += a; 151 | m_hash[1] += b; 152 | m_hash[2] += c; 153 | m_hash[3] += d; 154 | m_hash[4] += e; 155 | } 156 | 157 | /// add arbitrary number of bytes 158 | void SHA1::add(const void *data, size_t numBytes) { 159 | const uint8_t *current = (const uint8_t *)data; 160 | 161 | if (m_bufferSize > 0) { 162 | while (numBytes > 0 && m_bufferSize < BlockSize) { 163 | m_buffer[m_bufferSize++] = *current++; 164 | numBytes--; 165 | } 166 | } 167 | 168 | // full buffer 169 | if (m_bufferSize == BlockSize) { 170 | processBlock((void *)m_buffer); 171 | m_numBytes += BlockSize; 172 | m_bufferSize = 0; 173 | } 174 | 175 | // no more data ? 176 | if (numBytes == 0) 177 | return; 178 | 179 | // process full blocks 180 | while (numBytes >= BlockSize) { 181 | processBlock(current); 182 | current += BlockSize; 183 | m_numBytes += BlockSize; 184 | numBytes -= BlockSize; 185 | } 186 | 187 | // keep remaining bytes in buffer 188 | while (numBytes > 0) { 189 | m_buffer[m_bufferSize++] = *current++; 190 | numBytes--; 191 | } 192 | } 193 | 194 | /// process final block, less than 64 bytes 195 | void SHA1::processBuffer() { 196 | // the input bytes are considered as bits strings, where the first bit is the most significant bit of the byte 197 | 198 | // - append "1" bit to message 199 | // - append "0" bits until message length in bit mod 512 is 448 200 | // - append length as 64 bit integer 201 | 202 | // number of bits 203 | size_t paddedLength = m_bufferSize * 8; 204 | 205 | // plus one bit set to 1 (always appended) 206 | paddedLength++; 207 | 208 | // number of bits must be (numBits % 512) = 448 209 | size_t lower11Bits = paddedLength & 511; 210 | if (lower11Bits <= 448) 211 | paddedLength += 448 - lower11Bits; 212 | else 213 | paddedLength += 512 + 448 - lower11Bits; 214 | // convert from bits to bytes 215 | paddedLength /= 8; 216 | 217 | // only needed if additional data flows over into a second block 218 | unsigned char extra[BlockSize]; 219 | 220 | // append a "1" bit, 128 => binary 10000000 221 | if (m_bufferSize < BlockSize) 222 | m_buffer[m_bufferSize] = 128; 223 | else 224 | extra[0] = 128; 225 | 226 | size_t i; 227 | for (i = m_bufferSize + 1; i < BlockSize; i++) 228 | m_buffer[i] = 0; 229 | for (; i < paddedLength; i++) 230 | extra[i - BlockSize] = 0; 231 | 232 | // add message length in bits as 64 bit number 233 | uint64_t msgBits = 8 * (m_numBytes + m_bufferSize); 234 | // find right position 235 | unsigned char *addLength; 236 | if (paddedLength < BlockSize) 237 | addLength = m_buffer + paddedLength; 238 | else 239 | addLength = extra + paddedLength - BlockSize; 240 | 241 | // must be big endian 242 | *addLength++ = (unsigned char)((msgBits >> 56) & 0xFF); 243 | *addLength++ = (unsigned char)((msgBits >> 48) & 0xFF); 244 | *addLength++ = (unsigned char)((msgBits >> 40) & 0xFF); 245 | *addLength++ = (unsigned char)((msgBits >> 32) & 0xFF); 246 | *addLength++ = (unsigned char)((msgBits >> 24) & 0xFF); 247 | *addLength++ = (unsigned char)((msgBits >> 16) & 0xFF); 248 | *addLength++ = (unsigned char)((msgBits >> 8) & 0xFF); 249 | *addLength = (unsigned char)(msgBits & 0xFF); 250 | 251 | // process blocks 252 | processBlock(m_buffer); 253 | // flowed over into a second block ? 254 | if (paddedLength > BlockSize) 255 | processBlock(extra); 256 | } 257 | 258 | /// return latest hash as 40 hex characters 259 | std::string SHA1::getHash() { 260 | // compute hash (as raw bytes) 261 | unsigned char rawHash[HashBytes]; 262 | getHash(rawHash); 263 | 264 | // convert to hex string 265 | std::string result; 266 | result.reserve(2 * HashBytes); 267 | for (int i = 0; i < HashBytes; i++) { 268 | static const char dec2hex[16 + 1] = "0123456789abcdef"; 269 | result += dec2hex[(rawHash[i] >> 4) & 15]; 270 | result += dec2hex[rawHash[i] & 15]; 271 | } 272 | 273 | return result; 274 | } 275 | 276 | /// return latest hash as bytes 277 | void SHA1::getHash(unsigned char buffer[SHA1::HashBytes]) { 278 | // save old hash if buffer is partially filled 279 | uint32_t oldHash[HashValues]; 280 | for (int i = 0; i < HashValues; i++) 281 | oldHash[i] = m_hash[i]; 282 | 283 | // process remaining bytes 284 | processBuffer(); 285 | 286 | unsigned char *current = buffer; 287 | for (int i = 0; i < HashValues; i++) { 288 | *current++ = (m_hash[i] >> 24) & 0xFF; 289 | *current++ = (m_hash[i] >> 16) & 0xFF; 290 | *current++ = (m_hash[i] >> 8) & 0xFF; 291 | *current++ = m_hash[i] & 0xFF; 292 | 293 | // restore old hash 294 | m_hash[i] = oldHash[i]; 295 | } 296 | } 297 | 298 | /// compute SHA1 of a memory block 299 | std::string SHA1::operator()(const void *data, size_t numBytes) { 300 | reset(); 301 | add(data, numBytes); 302 | return getHash(); 303 | } 304 | 305 | /// compute SHA1 of a string, excluding final zero 306 | std::string SHA1::operator()(const std::string &text) { 307 | reset(); 308 | add(text.c_str(), text.size()); 309 | return getHash(); 310 | } 311 | -------------------------------------------------------------------------------- /src/sha256.cpp: -------------------------------------------------------------------------------- 1 | // ////////////////////////////////////////////////////////// 2 | // sha256.cpp 3 | // Copyright (c) 2014,2015,2021 Stephan Brumme. All rights reserved. 4 | // see http://create.stephan-brumme.com/disclaimer.html 5 | // 6 | 7 | #include "sha256.h" 8 | 9 | // big endian architectures need #define __BYTE_ORDER __BIG_ENDIAN 10 | #ifndef _MSC_VER 11 | #include 12 | #endif 13 | 14 | //#define SHA2_224_SEED_VECTOR 15 | 16 | /// same as reset() 17 | SHA256::SHA256() { 18 | reset(); 19 | } 20 | 21 | /// restart 22 | void SHA256::reset() { 23 | m_numBytes = 0; 24 | m_bufferSize = 0; 25 | 26 | // according to RFC 1321 27 | // "These words were obtained by taking the first thirty-two bits of the 28 | // fractional parts of the square roots of the first eight prime numbers" 29 | m_hash[0] = 0x6a09e667; 30 | m_hash[1] = 0xbb67ae85; 31 | m_hash[2] = 0x3c6ef372; 32 | m_hash[3] = 0xa54ff53a; 33 | m_hash[4] = 0x510e527f; 34 | m_hash[5] = 0x9b05688c; 35 | m_hash[6] = 0x1f83d9ab; 36 | m_hash[7] = 0x5be0cd19; 37 | 38 | #ifdef SHA2_224_SEED_VECTOR 39 | // if you want SHA2-224 instead then use these seeds 40 | // and throw away the last 32 bits of getHash 41 | m_hash[0] = 0xc1059ed8; 42 | m_hash[1] = 0x367cd507; 43 | m_hash[2] = 0x3070dd17; 44 | m_hash[3] = 0xf70e5939; 45 | m_hash[4] = 0xffc00b31; 46 | m_hash[5] = 0x68581511; 47 | m_hash[6] = 0x64f98fa7; 48 | m_hash[7] = 0xbefa4fa4; 49 | #endif 50 | } 51 | 52 | namespace { 53 | inline uint32_t rotate(uint32_t a, uint32_t c) { 54 | return (a >> c) | (a << (32 - c)); 55 | } 56 | 57 | inline uint32_t swap(uint32_t x) { 58 | #if defined(__GNUC__) || defined(__clang__) 59 | return __builtin_bswap32(x); 60 | #endif 61 | #ifdef MSC_VER 62 | return _byteswap_ulong(x); 63 | #endif 64 | 65 | return (x >> 24) | 66 | ((x >> 8) & 0x0000FF00) | 67 | ((x << 8) & 0x00FF0000) | 68 | (x << 24); 69 | } 70 | 71 | // mix functions for processBlock() 72 | inline uint32_t f1(uint32_t e, uint32_t f, uint32_t g) { 73 | uint32_t term1 = rotate(e, 6) ^ rotate(e, 11) ^ rotate(e, 25); 74 | uint32_t term2 = (e & f) ^ (~e & g); //(g ^ (e & (f ^ g))) 75 | return term1 + term2; 76 | } 77 | 78 | inline uint32_t f2(uint32_t a, uint32_t b, uint32_t c) { 79 | uint32_t term1 = rotate(a, 2) ^ rotate(a, 13) ^ rotate(a, 22); 80 | uint32_t term2 = ((a | b) & c) | (a & b); //(a & (b ^ c)) ^ (b & c); 81 | return term1 + term2; 82 | } 83 | } // namespace 84 | 85 | /// process 64 bytes 86 | void SHA256::processBlock(const void *data) { 87 | // get last hash 88 | uint32_t a = m_hash[0]; 89 | uint32_t b = m_hash[1]; 90 | uint32_t c = m_hash[2]; 91 | uint32_t d = m_hash[3]; 92 | uint32_t e = m_hash[4]; 93 | uint32_t f = m_hash[5]; 94 | uint32_t g = m_hash[6]; 95 | uint32_t h = m_hash[7]; 96 | 97 | // data represented as 16x 32-bit words 98 | const uint32_t *input = (uint32_t *)data; 99 | // convert to big endian 100 | uint32_t words[64]; 101 | int i; 102 | for (i = 0; i < 16; i++) 103 | #if defined(__BYTE_ORDER) && (__BYTE_ORDER != 0) && (__BYTE_ORDER == __BIG_ENDIAN) 104 | words[i] = input[i]; 105 | #else 106 | words[i] = swap(input[i]); 107 | #endif 108 | 109 | uint32_t x, y; // temporaries 110 | 111 | // first round 112 | x = h + f1(e, f, g) + 0x428a2f98 + words[0]; 113 | y = f2(a, b, c); 114 | d += x; 115 | h = x + y; 116 | x = g + f1(d, e, f) + 0x71374491 + words[1]; 117 | y = f2(h, a, b); 118 | c += x; 119 | g = x + y; 120 | x = f + f1(c, d, e) + 0xb5c0fbcf + words[2]; 121 | y = f2(g, h, a); 122 | b += x; 123 | f = x + y; 124 | x = e + f1(b, c, d) + 0xe9b5dba5 + words[3]; 125 | y = f2(f, g, h); 126 | a += x; 127 | e = x + y; 128 | x = d + f1(a, b, c) + 0x3956c25b + words[4]; 129 | y = f2(e, f, g); 130 | h += x; 131 | d = x + y; 132 | x = c + f1(h, a, b) + 0x59f111f1 + words[5]; 133 | y = f2(d, e, f); 134 | g += x; 135 | c = x + y; 136 | x = b + f1(g, h, a) + 0x923f82a4 + words[6]; 137 | y = f2(c, d, e); 138 | f += x; 139 | b = x + y; 140 | x = a + f1(f, g, h) + 0xab1c5ed5 + words[7]; 141 | y = f2(b, c, d); 142 | e += x; 143 | a = x + y; 144 | 145 | // secound round 146 | x = h + f1(e, f, g) + 0xd807aa98 + words[8]; 147 | y = f2(a, b, c); 148 | d += x; 149 | h = x + y; 150 | x = g + f1(d, e, f) + 0x12835b01 + words[9]; 151 | y = f2(h, a, b); 152 | c += x; 153 | g = x + y; 154 | x = f + f1(c, d, e) + 0x243185be + words[10]; 155 | y = f2(g, h, a); 156 | b += x; 157 | f = x + y; 158 | x = e + f1(b, c, d) + 0x550c7dc3 + words[11]; 159 | y = f2(f, g, h); 160 | a += x; 161 | e = x + y; 162 | x = d + f1(a, b, c) + 0x72be5d74 + words[12]; 163 | y = f2(e, f, g); 164 | h += x; 165 | d = x + y; 166 | x = c + f1(h, a, b) + 0x80deb1fe + words[13]; 167 | y = f2(d, e, f); 168 | g += x; 169 | c = x + y; 170 | x = b + f1(g, h, a) + 0x9bdc06a7 + words[14]; 171 | y = f2(c, d, e); 172 | f += x; 173 | b = x + y; 174 | x = a + f1(f, g, h) + 0xc19bf174 + words[15]; 175 | y = f2(b, c, d); 176 | e += x; 177 | a = x + y; 178 | 179 | // extend to 24 words 180 | for (; i < 24; i++) 181 | words[i] = words[i - 16] + 182 | (rotate(words[i - 15], 7) ^ rotate(words[i - 15], 18) ^ (words[i - 15] >> 3)) + 183 | words[i - 7] + 184 | (rotate(words[i - 2], 17) ^ rotate(words[i - 2], 19) ^ (words[i - 2] >> 10)); 185 | 186 | // third round 187 | x = h + f1(e, f, g) + 0xe49b69c1 + words[16]; 188 | y = f2(a, b, c); 189 | d += x; 190 | h = x + y; 191 | x = g + f1(d, e, f) + 0xefbe4786 + words[17]; 192 | y = f2(h, a, b); 193 | c += x; 194 | g = x + y; 195 | x = f + f1(c, d, e) + 0x0fc19dc6 + words[18]; 196 | y = f2(g, h, a); 197 | b += x; 198 | f = x + y; 199 | x = e + f1(b, c, d) + 0x240ca1cc + words[19]; 200 | y = f2(f, g, h); 201 | a += x; 202 | e = x + y; 203 | x = d + f1(a, b, c) + 0x2de92c6f + words[20]; 204 | y = f2(e, f, g); 205 | h += x; 206 | d = x + y; 207 | x = c + f1(h, a, b) + 0x4a7484aa + words[21]; 208 | y = f2(d, e, f); 209 | g += x; 210 | c = x + y; 211 | x = b + f1(g, h, a) + 0x5cb0a9dc + words[22]; 212 | y = f2(c, d, e); 213 | f += x; 214 | b = x + y; 215 | x = a + f1(f, g, h) + 0x76f988da + words[23]; 216 | y = f2(b, c, d); 217 | e += x; 218 | a = x + y; 219 | 220 | // extend to 32 words 221 | for (; i < 32; i++) 222 | words[i] = words[i - 16] + 223 | (rotate(words[i - 15], 7) ^ rotate(words[i - 15], 18) ^ (words[i - 15] >> 3)) + 224 | words[i - 7] + 225 | (rotate(words[i - 2], 17) ^ rotate(words[i - 2], 19) ^ (words[i - 2] >> 10)); 226 | 227 | // fourth round 228 | x = h + f1(e, f, g) + 0x983e5152 + words[24]; 229 | y = f2(a, b, c); 230 | d += x; 231 | h = x + y; 232 | x = g + f1(d, e, f) + 0xa831c66d + words[25]; 233 | y = f2(h, a, b); 234 | c += x; 235 | g = x + y; 236 | x = f + f1(c, d, e) + 0xb00327c8 + words[26]; 237 | y = f2(g, h, a); 238 | b += x; 239 | f = x + y; 240 | x = e + f1(b, c, d) + 0xbf597fc7 + words[27]; 241 | y = f2(f, g, h); 242 | a += x; 243 | e = x + y; 244 | x = d + f1(a, b, c) + 0xc6e00bf3 + words[28]; 245 | y = f2(e, f, g); 246 | h += x; 247 | d = x + y; 248 | x = c + f1(h, a, b) + 0xd5a79147 + words[29]; 249 | y = f2(d, e, f); 250 | g += x; 251 | c = x + y; 252 | x = b + f1(g, h, a) + 0x06ca6351 + words[30]; 253 | y = f2(c, d, e); 254 | f += x; 255 | b = x + y; 256 | x = a + f1(f, g, h) + 0x14292967 + words[31]; 257 | y = f2(b, c, d); 258 | e += x; 259 | a = x + y; 260 | 261 | // extend to 40 words 262 | for (; i < 40; i++) 263 | words[i] = words[i - 16] + 264 | (rotate(words[i - 15], 7) ^ rotate(words[i - 15], 18) ^ (words[i - 15] >> 3)) + 265 | words[i - 7] + 266 | (rotate(words[i - 2], 17) ^ rotate(words[i - 2], 19) ^ (words[i - 2] >> 10)); 267 | 268 | // fifth round 269 | x = h + f1(e, f, g) + 0x27b70a85 + words[32]; 270 | y = f2(a, b, c); 271 | d += x; 272 | h = x + y; 273 | x = g + f1(d, e, f) + 0x2e1b2138 + words[33]; 274 | y = f2(h, a, b); 275 | c += x; 276 | g = x + y; 277 | x = f + f1(c, d, e) + 0x4d2c6dfc + words[34]; 278 | y = f2(g, h, a); 279 | b += x; 280 | f = x + y; 281 | x = e + f1(b, c, d) + 0x53380d13 + words[35]; 282 | y = f2(f, g, h); 283 | a += x; 284 | e = x + y; 285 | x = d + f1(a, b, c) + 0x650a7354 + words[36]; 286 | y = f2(e, f, g); 287 | h += x; 288 | d = x + y; 289 | x = c + f1(h, a, b) + 0x766a0abb + words[37]; 290 | y = f2(d, e, f); 291 | g += x; 292 | c = x + y; 293 | x = b + f1(g, h, a) + 0x81c2c92e + words[38]; 294 | y = f2(c, d, e); 295 | f += x; 296 | b = x + y; 297 | x = a + f1(f, g, h) + 0x92722c85 + words[39]; 298 | y = f2(b, c, d); 299 | e += x; 300 | a = x + y; 301 | 302 | // extend to 48 words 303 | for (; i < 48; i++) 304 | words[i] = words[i - 16] + 305 | (rotate(words[i - 15], 7) ^ rotate(words[i - 15], 18) ^ (words[i - 15] >> 3)) + 306 | words[i - 7] + 307 | (rotate(words[i - 2], 17) ^ rotate(words[i - 2], 19) ^ (words[i - 2] >> 10)); 308 | 309 | // sixth round 310 | x = h + f1(e, f, g) + 0xa2bfe8a1 + words[40]; 311 | y = f2(a, b, c); 312 | d += x; 313 | h = x + y; 314 | x = g + f1(d, e, f) + 0xa81a664b + words[41]; 315 | y = f2(h, a, b); 316 | c += x; 317 | g = x + y; 318 | x = f + f1(c, d, e) + 0xc24b8b70 + words[42]; 319 | y = f2(g, h, a); 320 | b += x; 321 | f = x + y; 322 | x = e + f1(b, c, d) + 0xc76c51a3 + words[43]; 323 | y = f2(f, g, h); 324 | a += x; 325 | e = x + y; 326 | x = d + f1(a, b, c) + 0xd192e819 + words[44]; 327 | y = f2(e, f, g); 328 | h += x; 329 | d = x + y; 330 | x = c + f1(h, a, b) + 0xd6990624 + words[45]; 331 | y = f2(d, e, f); 332 | g += x; 333 | c = x + y; 334 | x = b + f1(g, h, a) + 0xf40e3585 + words[46]; 335 | y = f2(c, d, e); 336 | f += x; 337 | b = x + y; 338 | x = a + f1(f, g, h) + 0x106aa070 + words[47]; 339 | y = f2(b, c, d); 340 | e += x; 341 | a = x + y; 342 | 343 | // extend to 56 words 344 | for (; i < 56; i++) 345 | words[i] = words[i - 16] + 346 | (rotate(words[i - 15], 7) ^ rotate(words[i - 15], 18) ^ (words[i - 15] >> 3)) + 347 | words[i - 7] + 348 | (rotate(words[i - 2], 17) ^ rotate(words[i - 2], 19) ^ (words[i - 2] >> 10)); 349 | 350 | // seventh round 351 | x = h + f1(e, f, g) + 0x19a4c116 + words[48]; 352 | y = f2(a, b, c); 353 | d += x; 354 | h = x + y; 355 | x = g + f1(d, e, f) + 0x1e376c08 + words[49]; 356 | y = f2(h, a, b); 357 | c += x; 358 | g = x + y; 359 | x = f + f1(c, d, e) + 0x2748774c + words[50]; 360 | y = f2(g, h, a); 361 | b += x; 362 | f = x + y; 363 | x = e + f1(b, c, d) + 0x34b0bcb5 + words[51]; 364 | y = f2(f, g, h); 365 | a += x; 366 | e = x + y; 367 | x = d + f1(a, b, c) + 0x391c0cb3 + words[52]; 368 | y = f2(e, f, g); 369 | h += x; 370 | d = x + y; 371 | x = c + f1(h, a, b) + 0x4ed8aa4a + words[53]; 372 | y = f2(d, e, f); 373 | g += x; 374 | c = x + y; 375 | x = b + f1(g, h, a) + 0x5b9cca4f + words[54]; 376 | y = f2(c, d, e); 377 | f += x; 378 | b = x + y; 379 | x = a + f1(f, g, h) + 0x682e6ff3 + words[55]; 380 | y = f2(b, c, d); 381 | e += x; 382 | a = x + y; 383 | 384 | // extend to 64 words 385 | for (; i < 64; i++) 386 | words[i] = words[i - 16] + 387 | (rotate(words[i - 15], 7) ^ rotate(words[i - 15], 18) ^ (words[i - 15] >> 3)) + 388 | words[i - 7] + 389 | (rotate(words[i - 2], 17) ^ rotate(words[i - 2], 19) ^ (words[i - 2] >> 10)); 390 | 391 | // eigth round 392 | x = h + f1(e, f, g) + 0x748f82ee + words[56]; 393 | y = f2(a, b, c); 394 | d += x; 395 | h = x + y; 396 | x = g + f1(d, e, f) + 0x78a5636f + words[57]; 397 | y = f2(h, a, b); 398 | c += x; 399 | g = x + y; 400 | x = f + f1(c, d, e) + 0x84c87814 + words[58]; 401 | y = f2(g, h, a); 402 | b += x; 403 | f = x + y; 404 | x = e + f1(b, c, d) + 0x8cc70208 + words[59]; 405 | y = f2(f, g, h); 406 | a += x; 407 | e = x + y; 408 | x = d + f1(a, b, c) + 0x90befffa + words[60]; 409 | y = f2(e, f, g); 410 | h += x; 411 | d = x + y; 412 | x = c + f1(h, a, b) + 0xa4506ceb + words[61]; 413 | y = f2(d, e, f); 414 | g += x; 415 | c = x + y; 416 | x = b + f1(g, h, a) + 0xbef9a3f7 + words[62]; 417 | y = f2(c, d, e); 418 | f += x; 419 | b = x + y; 420 | x = a + f1(f, g, h) + 0xc67178f2 + words[63]; 421 | y = f2(b, c, d); 422 | e += x; 423 | a = x + y; 424 | 425 | // update hash 426 | m_hash[0] += a; 427 | m_hash[1] += b; 428 | m_hash[2] += c; 429 | m_hash[3] += d; 430 | m_hash[4] += e; 431 | m_hash[5] += f; 432 | m_hash[6] += g; 433 | m_hash[7] += h; 434 | } 435 | 436 | /// add arbitrary number of bytes 437 | void SHA256::add(const void *data, size_t numBytes) { 438 | const uint8_t *current = (const uint8_t *)data; 439 | 440 | if (m_bufferSize > 0) { 441 | while (numBytes > 0 && m_bufferSize < BlockSize) { 442 | m_buffer[m_bufferSize++] = *current++; 443 | numBytes--; 444 | } 445 | } 446 | 447 | // full buffer 448 | if (m_bufferSize == BlockSize) { 449 | processBlock(m_buffer); 450 | m_numBytes += BlockSize; 451 | m_bufferSize = 0; 452 | } 453 | 454 | // no more data ? 455 | if (numBytes == 0) 456 | return; 457 | 458 | // process full blocks 459 | while (numBytes >= BlockSize) { 460 | processBlock(current); 461 | current += BlockSize; 462 | m_numBytes += BlockSize; 463 | numBytes -= BlockSize; 464 | } 465 | 466 | // keep remaining bytes in buffer 467 | while (numBytes > 0) { 468 | m_buffer[m_bufferSize++] = *current++; 469 | numBytes--; 470 | } 471 | } 472 | 473 | /// process final block, less than 64 bytes 474 | void SHA256::processBuffer() { 475 | // the input bytes are considered as bits strings, where the first bit is the most significant bit of the byte 476 | 477 | // - append "1" bit to message 478 | // - append "0" bits until message length in bit mod 512 is 448 479 | // - append length as 64 bit integer 480 | 481 | // number of bits 482 | size_t paddedLength = m_bufferSize * 8; 483 | 484 | // plus one bit set to 1 (always appended) 485 | paddedLength++; 486 | 487 | // number of bits must be (numBits % 512) = 448 488 | size_t lower11Bits = paddedLength & 511; 489 | if (lower11Bits <= 448) 490 | paddedLength += 448 - lower11Bits; 491 | else 492 | paddedLength += 512 + 448 - lower11Bits; 493 | // convert from bits to bytes 494 | paddedLength /= 8; 495 | 496 | // only needed if additional data flows over into a second block 497 | unsigned char extra[BlockSize]; 498 | 499 | // append a "1" bit, 128 => binary 10000000 500 | if (m_bufferSize < BlockSize) 501 | m_buffer[m_bufferSize] = 128; 502 | else 503 | extra[0] = 128; 504 | 505 | size_t i; 506 | for (i = m_bufferSize + 1; i < BlockSize; i++) 507 | m_buffer[i] = 0; 508 | for (; i < paddedLength; i++) 509 | extra[i - BlockSize] = 0; 510 | 511 | // add message length in bits as 64 bit number 512 | uint64_t msgBits = 8 * (m_numBytes + m_bufferSize); 513 | // find right position 514 | unsigned char *addLength; 515 | if (paddedLength < BlockSize) 516 | addLength = m_buffer + paddedLength; 517 | else 518 | addLength = extra + paddedLength - BlockSize; 519 | 520 | // must be big endian 521 | *addLength++ = (unsigned char)((msgBits >> 56) & 0xFF); 522 | *addLength++ = (unsigned char)((msgBits >> 48) & 0xFF); 523 | *addLength++ = (unsigned char)((msgBits >> 40) & 0xFF); 524 | *addLength++ = (unsigned char)((msgBits >> 32) & 0xFF); 525 | *addLength++ = (unsigned char)((msgBits >> 24) & 0xFF); 526 | *addLength++ = (unsigned char)((msgBits >> 16) & 0xFF); 527 | *addLength++ = (unsigned char)((msgBits >> 8) & 0xFF); 528 | *addLength = (unsigned char)(msgBits & 0xFF); 529 | 530 | // process blocks 531 | processBlock(m_buffer); 532 | // flowed over into a second block ? 533 | if (paddedLength > BlockSize) 534 | processBlock(extra); 535 | } 536 | 537 | /// return latest hash as 64 hex characters 538 | std::string SHA256::getHash() { 539 | // compute hash (as raw bytes) 540 | unsigned char rawHash[HashBytes]; 541 | getHash(rawHash); 542 | 543 | // convert to hex string 544 | std::string result; 545 | result.reserve(2 * HashBytes); 546 | for (int i = 0; i < HashBytes; i++) { 547 | static const char dec2hex[16 + 1] = "0123456789abcdef"; 548 | result += dec2hex[(rawHash[i] >> 4) & 15]; 549 | result += dec2hex[rawHash[i] & 15]; 550 | } 551 | 552 | return result; 553 | } 554 | 555 | /// return latest hash as bytes 556 | void SHA256::getHash(unsigned char buffer[SHA256::HashBytes]) { 557 | // save old hash if buffer is partially filled 558 | uint32_t oldHash[HashValues]; 559 | for (int i = 0; i < HashValues; i++) 560 | oldHash[i] = m_hash[i]; 561 | 562 | // process remaining bytes 563 | processBuffer(); 564 | 565 | unsigned char *current = buffer; 566 | for (int i = 0; i < HashValues; i++) { 567 | *current++ = (m_hash[i] >> 24) & 0xFF; 568 | *current++ = (m_hash[i] >> 16) & 0xFF; 569 | *current++ = (m_hash[i] >> 8) & 0xFF; 570 | *current++ = m_hash[i] & 0xFF; 571 | 572 | // restore old hash 573 | m_hash[i] = oldHash[i]; 574 | } 575 | } 576 | 577 | /// compute SHA256 of a memory block 578 | std::string SHA256::operator()(const void *data, size_t numBytes) { 579 | reset(); 580 | add(data, numBytes); 581 | return getHash(); 582 | } 583 | 584 | /// compute SHA256 of a string, excluding final zero 585 | std::string SHA256::operator()(const std::string &text) { 586 | reset(); 587 | add(text.c_str(), text.size()); 588 | return getHash(); 589 | } 590 | -------------------------------------------------------------------------------- /tests/dump_test.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Al Azif 2 | // License: GPLv3 3 | 4 | #ifndef DUMPER_TESTS_DUMP_TEST_H_ 5 | #define DUMPER_TESTS_DUMP_TEST_H_ 6 | 7 | #include "dump.h" 8 | 9 | #include 10 | 11 | #include "testing.h" 12 | 13 | TEST(dumpTests, dump) { 14 | // TODO 15 | } 16 | 17 | TEST(dumpTests, dumpBase) { 18 | // TODO 19 | } 20 | 21 | TEST(dumpTests, dumpPatch) { 22 | // TODO 23 | } 24 | 25 | TEST(dumpTests, dumpRemaster) { 26 | // TODO 27 | } 28 | 29 | TEST(dumpTests, dumpTheme) { 30 | // TODO 31 | } 32 | 33 | TEST(dumpTests, dumpThemeUnlock) { 34 | // TODO 35 | } 36 | 37 | TEST(dumpTests, dumpAc) { 38 | // TODO 39 | } 40 | 41 | TEST(dumpTests, dumpAcNoData) { 42 | // TODO 43 | } 44 | 45 | #endif // DUMPER_TESTS_DUMP_TEST_H_ 46 | -------------------------------------------------------------------------------- /tests/files/elf/brokenElfMagic.elf: -------------------------------------------------------------------------------- 1 | XXXX -------------------------------------------------------------------------------- /tests/files/elf/brokenElfMagic.self: -------------------------------------------------------------------------------- 1 | O=XXXX@8 -------------------------------------------------------------------------------- /tests/files/elf/brokenElfSize.elf: -------------------------------------------------------------------------------- 1 | ELF -------------------------------------------------------------------------------- /tests/files/elf/brokenElfSize.self: -------------------------------------------------------------------------------- 1 | O=ELF@8 -------------------------------------------------------------------------------- /tests/files/elf/brokenSceHeader.self: -------------------------------------------------------------------------------- 1 | O= ELF@8 -------------------------------------------------------------------------------- /tests/files/elf/brokenSceHeaderNpdrm.self: -------------------------------------------------------------------------------- 1 | O=ELF@8 -------------------------------------------------------------------------------- /tests/files/elf/brokenSelfMagic.self: -------------------------------------------------------------------------------- 1 | XXXXELF@8 -------------------------------------------------------------------------------- /tests/files/elf/brokenSelfSize.self: -------------------------------------------------------------------------------- 1 | O= -------------------------------------------------------------------------------- /tests/files/elf/getAppVersion_0s.self: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Al-Azif/dumper-testing/1195837e6ff97c080792a15bfca80ef0edd6e94b/tests/files/elf/getAppVersion_0s.self -------------------------------------------------------------------------------- /tests/files/elf/getAppVersion_0s_(NPDRM_Header).self: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Al-Azif/dumper-testing/1195837e6ff97c080792a15bfca80ef0edd6e94b/tests/files/elf/getAppVersion_0s_(NPDRM_Header).self -------------------------------------------------------------------------------- /tests/files/elf/getAppVersion_Fs.self: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Al-Azif/dumper-testing/1195837e6ff97c080792a15bfca80ef0edd6e94b/tests/files/elf/getAppVersion_Fs.self -------------------------------------------------------------------------------- /tests/files/elf/getAppVersion_Fs_(NPDRM_Header).self: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Al-Azif/dumper-testing/1195837e6ff97c080792a15bfca80ef0edd6e94b/tests/files/elf/getAppVersion_Fs_(NPDRM_Header).self -------------------------------------------------------------------------------- /tests/files/elf/getDigest_0s.self: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Al-Azif/dumper-testing/1195837e6ff97c080792a15bfca80ef0edd6e94b/tests/files/elf/getDigest_0s.self -------------------------------------------------------------------------------- /tests/files/elf/getDigest_0s_(NPDRM_Header).self: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Al-Azif/dumper-testing/1195837e6ff97c080792a15bfca80ef0edd6e94b/tests/files/elf/getDigest_0s_(NPDRM_Header).self -------------------------------------------------------------------------------- /tests/files/elf/getDigest_Fs.self: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Al-Azif/dumper-testing/1195837e6ff97c080792a15bfca80ef0edd6e94b/tests/files/elf/getDigest_Fs.self -------------------------------------------------------------------------------- /tests/files/elf/getDigest_Fs_(NPDRM_Header).self: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Al-Azif/dumper-testing/1195837e6ff97c080792a15bfca80ef0edd6e94b/tests/files/elf/getDigest_Fs_(NPDRM_Header).self -------------------------------------------------------------------------------- /tests/files/elf/getFwVersion_0s.self: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Al-Azif/dumper-testing/1195837e6ff97c080792a15bfca80ef0edd6e94b/tests/files/elf/getFwVersion_0s.self -------------------------------------------------------------------------------- /tests/files/elf/getFwVersion_0s_(NPDRM_Header).self: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Al-Azif/dumper-testing/1195837e6ff97c080792a15bfca80ef0edd6e94b/tests/files/elf/getFwVersion_0s_(NPDRM_Header).self -------------------------------------------------------------------------------- /tests/files/elf/getFwVersion_Fs.self: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Al-Azif/dumper-testing/1195837e6ff97c080792a15bfca80ef0edd6e94b/tests/files/elf/getFwVersion_Fs.self -------------------------------------------------------------------------------- /tests/files/elf/getFwVersion_Fs_(NPDRM_Header).self: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Al-Azif/dumper-testing/1195837e6ff97c080792a15bfca80ef0edd6e94b/tests/files/elf/getFwVersion_Fs_(NPDRM_Header).self -------------------------------------------------------------------------------- /tests/files/elf/getPaid_0s.self: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Al-Azif/dumper-testing/1195837e6ff97c080792a15bfca80ef0edd6e94b/tests/files/elf/getPaid_0s.self -------------------------------------------------------------------------------- /tests/files/elf/getPaid_0s_(NPDRM_Header).self: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Al-Azif/dumper-testing/1195837e6ff97c080792a15bfca80ef0edd6e94b/tests/files/elf/getPaid_0s_(NPDRM_Header).self -------------------------------------------------------------------------------- /tests/files/elf/getPaid_Fs.self: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Al-Azif/dumper-testing/1195837e6ff97c080792a15bfca80ef0edd6e94b/tests/files/elf/getPaid_Fs.self -------------------------------------------------------------------------------- /tests/files/elf/getPaid_Fs_(NPDRM_Header).self: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Al-Azif/dumper-testing/1195837e6ff97c080792a15bfca80ef0edd6e94b/tests/files/elf/getPaid_Fs_(NPDRM_Header).self -------------------------------------------------------------------------------- /tests/files/elf/isValidDecrypt_Decrypted_1.elf: -------------------------------------------------------------------------------- 1 | ELF -------------------------------------------------------------------------------- /tests/files/elf/isValidDecrypt_Decrypted_2.elf: -------------------------------------------------------------------------------- 1 | ELF -------------------------------------------------------------------------------- /tests/files/elf/isValidDecrypt_Decrypted_3.elf: -------------------------------------------------------------------------------- 1 | ELF -------------------------------------------------------------------------------- /tests/files/elf/isValidDecrypt_Decrypted_4.elf: -------------------------------------------------------------------------------- 1 | ELF -------------------------------------------------------------------------------- /tests/files/elf/isValidDecrypt_Encrypted_1.self: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Al-Azif/dumper-testing/1195837e6ff97c080792a15bfca80ef0edd6e94b/tests/files/elf/isValidDecrypt_Encrypted_1.self -------------------------------------------------------------------------------- /tests/files/elf/isValidDecrypt_Encrypted_1_(NPDRM_Header).self: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Al-Azif/dumper-testing/1195837e6ff97c080792a15bfca80ef0edd6e94b/tests/files/elf/isValidDecrypt_Encrypted_1_(NPDRM_Header).self -------------------------------------------------------------------------------- /tests/files/elf/isValidDecrypt_Encrypted_2.self: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Al-Azif/dumper-testing/1195837e6ff97c080792a15bfca80ef0edd6e94b/tests/files/elf/isValidDecrypt_Encrypted_2.self -------------------------------------------------------------------------------- /tests/files/elf/isValidDecrypt_Encrypted_2_(NPDRM_Header).self: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Al-Azif/dumper-testing/1195837e6ff97c080792a15bfca80ef0edd6e94b/tests/files/elf/isValidDecrypt_Encrypted_2_(NPDRM_Header).self -------------------------------------------------------------------------------- /tests/files/elf/isValidDecrypt_Encrypted_3.self: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Al-Azif/dumper-testing/1195837e6ff97c080792a15bfca80ef0edd6e94b/tests/files/elf/isValidDecrypt_Encrypted_3.self -------------------------------------------------------------------------------- /tests/files/elf/isValidDecrypt_Encrypted_3_(NPDRM_Header).self: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Al-Azif/dumper-testing/1195837e6ff97c080792a15bfca80ef0edd6e94b/tests/files/elf/isValidDecrypt_Encrypted_3_(NPDRM_Header).self -------------------------------------------------------------------------------- /tests/files/elf/isValidDecrypt_Encrypted_4.self: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Al-Azif/dumper-testing/1195837e6ff97c080792a15bfca80ef0edd6e94b/tests/files/elf/isValidDecrypt_Encrypted_4.self -------------------------------------------------------------------------------- /tests/files/elf/isValidDecrypt_Encrypted_4_(NPDRM_Header).self: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Al-Azif/dumper-testing/1195837e6ff97c080792a15bfca80ef0edd6e94b/tests/files/elf/isValidDecrypt_Encrypted_4_(NPDRM_Header).self -------------------------------------------------------------------------------- /tests/files/elf/noPermission.ext: -------------------------------------------------------------------------------- 1 | O= ELF@8 -------------------------------------------------------------------------------- /tests/files/elf/notAFile.ext/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Al-Azif/dumper-testing/1195837e6ff97c080792a15bfca80ef0edd6e94b/tests/files/elf/notAFile.ext/.gitkeep -------------------------------------------------------------------------------- /tests/files/elf/ptype_Fake.self: -------------------------------------------------------------------------------- 1 | O=ELF@8 -------------------------------------------------------------------------------- /tests/files/elf/ptype_HostKernel.self: -------------------------------------------------------------------------------- 1 | O= ELF@8 -------------------------------------------------------------------------------- /tests/files/elf/ptype_NpdrmDynlib.self: -------------------------------------------------------------------------------- 1 | O=ELF@8 -------------------------------------------------------------------------------- /tests/files/elf/ptype_NpdrmExec.self: -------------------------------------------------------------------------------- 1 | O=ELF@8 -------------------------------------------------------------------------------- /tests/files/elf/ptype_SecureKernel.self: -------------------------------------------------------------------------------- 1 | O=ELF@8 -------------------------------------------------------------------------------- /tests/files/elf/ptype_SecureModule.self: -------------------------------------------------------------------------------- 1 | O=ELF@8 -------------------------------------------------------------------------------- /tests/files/elf/ptype_SystemDynlib.self: -------------------------------------------------------------------------------- 1 | O= ELF@8 -------------------------------------------------------------------------------- /tests/files/elf/ptype_SystemExec.self: -------------------------------------------------------------------------------- 1 | O=ELF@8 -------------------------------------------------------------------------------- /tests/files/elf/ptype_Unknown.self: -------------------------------------------------------------------------------- 1 | O=ELF@8 -------------------------------------------------------------------------------- /tests/files/elf/sceHeaderOffset_0x410.self: -------------------------------------------------------------------------------- 1 | O= 2 | ELF@8 -------------------------------------------------------------------------------- /tests/files/elf/sceHeaderOffset_0xC0.self: -------------------------------------------------------------------------------- 1 | O=ELF@8 -------------------------------------------------------------------------------- /tests/files/elf/sectionHeaderZero.elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Al-Azif/dumper-testing/1195837e6ff97c080792a15bfca80ef0edd6e94b/tests/files/elf/sectionHeaderZero.elf -------------------------------------------------------------------------------- /tests/files/elf/valid.elf: -------------------------------------------------------------------------------- 1 | ELF -------------------------------------------------------------------------------- /tests/files/elf/valid.self: -------------------------------------------------------------------------------- 1 | O= -------------------------------------------------------------------------------- /tests/files/npbind/brokenNpbindDigest.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Al-Azif/dumper-testing/1195837e6ff97c080792a15bfca80ef0edd6e94b/tests/files/npbind/brokenNpbindDigest.dat -------------------------------------------------------------------------------- /tests/files/npbind/brokenNpbindEntriesSize.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Al-Azif/dumper-testing/1195837e6ff97c080792a15bfca80ef0edd6e94b/tests/files/npbind/brokenNpbindEntriesSize.dat -------------------------------------------------------------------------------- /tests/files/npbind/brokenNpbindHeaderSize.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Al-Azif/dumper-testing/1195837e6ff97c080792a15bfca80ef0edd6e94b/tests/files/npbind/brokenNpbindHeaderSize.dat -------------------------------------------------------------------------------- /tests/files/npbind/brokenNpbindMagic.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Al-Azif/dumper-testing/1195837e6ff97c080792a15bfca80ef0edd6e94b/tests/files/npbind/brokenNpbindMagic.dat -------------------------------------------------------------------------------- /tests/files/npbind/noPermission.ext: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Al-Azif/dumper-testing/1195837e6ff97c080792a15bfca80ef0edd6e94b/tests/files/npbind/noPermission.ext -------------------------------------------------------------------------------- /tests/files/npbind/notAFile.ext/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Al-Azif/dumper-testing/1195837e6ff97c080792a15bfca80ef0edd6e94b/tests/files/npbind/notAFile.ext/.gitkeep -------------------------------------------------------------------------------- /tests/files/npbind/valid_NoEntry.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Al-Azif/dumper-testing/1195837e6ff97c080792a15bfca80ef0edd6e94b/tests/files/npbind/valid_NoEntry.dat -------------------------------------------------------------------------------- /tests/files/npbind/valid_NoEntry_Alternative.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Al-Azif/dumper-testing/1195837e6ff97c080792a15bfca80ef0edd6e94b/tests/files/npbind/valid_NoEntry_Alternative.dat -------------------------------------------------------------------------------- /tests/files/npbind/valid_OneEntry.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Al-Azif/dumper-testing/1195837e6ff97c080792a15bfca80ef0edd6e94b/tests/files/npbind/valid_OneEntry.dat -------------------------------------------------------------------------------- /tests/files/npbind/valid_ThreeEntries.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Al-Azif/dumper-testing/1195837e6ff97c080792a15bfca80ef0edd6e94b/tests/files/npbind/valid_ThreeEntries.dat -------------------------------------------------------------------------------- /tests/files/npbind/valid_TwoEntries.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Al-Azif/dumper-testing/1195837e6ff97c080792a15bfca80ef0edd6e94b/tests/files/npbind/valid_TwoEntries.dat -------------------------------------------------------------------------------- /tests/files/pkg/noPermission.ext: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Al-Azif/dumper-testing/1195837e6ff97c080792a15bfca80ef0edd6e94b/tests/files/pkg/noPermission.ext -------------------------------------------------------------------------------- /tests/files/pkg/notADirectory.ext: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Al-Azif/dumper-testing/1195837e6ff97c080792a15bfca80ef0edd6e94b/tests/files/pkg/notADirectory.ext -------------------------------------------------------------------------------- /tests/files/pkg/notAFile.ext/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Al-Azif/dumper-testing/1195837e6ff97c080792a15bfca80ef0edd6e94b/tests/files/pkg/notAFile.ext/.gitkeep -------------------------------------------------------------------------------- /tests/files/sfo/noPermission.ext: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Al-Azif/dumper-testing/1195837e6ff97c080792a15bfca80ef0edd6e94b/tests/files/sfo/noPermission.ext -------------------------------------------------------------------------------- /tests/files/sfo/notAFile.ext/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Al-Azif/dumper-testing/1195837e6ff97c080792a15bfca80ef0edd6e94b/tests/files/sfo/notAFile.ext/.gitkeep -------------------------------------------------------------------------------- /tests/fself_test.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Al Azif 2 | // License: GPLv3 3 | 4 | #ifndef DUMPER_TESTS_FSELF_TEST_H_ 5 | #define DUMPER_TESTS_FSELF_TEST_H_ 6 | 7 | #include "fself.h" 8 | 9 | #include 10 | 11 | #include "testing.h" 12 | 13 | TEST(fselfTests, isFself) { 14 | // TODO 15 | } 16 | 17 | TEST(fselfTest, makeFself) { 18 | // TODO 19 | } 20 | 21 | #endif // DUMPER_TESTS_FSELF_TEST_H_ 22 | -------------------------------------------------------------------------------- /tests/gp4_test.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Al Azif 2 | // License: GPLv3 3 | 4 | #ifndef DUMPER_TESTS_GP4_TEST_H_ 5 | #define DUMPER_TESTS_GP4_TEST_H_ 6 | 7 | #include "gp4.h" 8 | 9 | #include 10 | 11 | #include "testing.h" 12 | 13 | TEST(gp4Tests, recursiveDirectory) { 14 | // TODO 15 | } 16 | 17 | TEST(gp4Tests, makeVolume) { 18 | // TODO 19 | } 20 | 21 | TEST(gp4Tests, makePlaygo) { 22 | // TODO 23 | } 24 | 25 | TEST(gp4Tests, makeFiles) { 26 | // TODO 27 | } 28 | 29 | TEST(gp4Tests, makeDirectories) { 30 | // TODO 31 | } 32 | 33 | TEST(gp4Tests, assemble) { 34 | // TODO 35 | } 36 | 37 | TEST(gp4Tests, write) { 38 | // TODO 39 | } 40 | 41 | TEST(gp4Tests, generate) { 42 | // TODO 43 | } 44 | 45 | #endif // DUMPER_TESTS_GP4_TEST_H_ 46 | -------------------------------------------------------------------------------- /tests/npbind_test.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Al Azif 2 | // License: GPLv3 3 | 4 | #ifndef DUMPER_TESTS_NPBIND_TEST_H_ 5 | #define DUMPER_TESTS_NPBIND_TEST_H_ 6 | 7 | #include "npbind.h" 8 | 9 | #include 10 | 11 | #include "testing.h" 12 | 13 | TEST(npbindTest, read) { 14 | // Empty input arguments 15 | EXPECT_EXCEPTION_REGEX(npbind::read(""), "^Error: Empty path argument! at \"npbind\\.cpp\":\\d*:\\(read\\)$", "Accepted empty argument"); // Empty 16 | EXPECT_EXCEPTION_REGEX(npbind::read(" "), "^Error: Empty path argument! at \"npbind\\.cpp\":\\d*:\\(read\\)$", "Accepted whitespace argument"); // Single space 17 | EXPECT_EXCEPTION_REGEX(npbind::read(" "), "^Error: Empty path argument! at \"npbind\\.cpp\":\\d*:\\(read\\)$", "Accepted whitespace argument"); // Double space 18 | EXPECT_EXCEPTION_REGEX(npbind::read("\t"), "^Error: Empty path argument! at \"npbind\\.cpp\":\\d*:\\(read\\)$", "Accepted whitespace argument"); // Single tab 19 | EXPECT_EXCEPTION_REGEX(npbind::read("\t\t"), "^Error: Empty path argument! at \"npbind\\.cpp\":\\d*:\\(read\\)$", "Accepted whitespace argument"); // Double tab 20 | EXPECT_EXCEPTION_REGEX(npbind::read(nullptr), "^basic_string::_M_construct null not valid$", "Accepted nullptr argument"); // nullptr 21 | 22 | // Non-existant file 23 | EXPECT_EXCEPTION_REGEX(npbind::read("./tests/files/npbind/doesNotExist.ext"), "^Error: Input path does not exist or is not a file! at \"npbind\\.cpp\":\\d*:\\(read\\)$", "Opened non-existant file"); 24 | 25 | // Open non-file object 26 | EXPECT_EXCEPTION_REGEX(npbind::read("./tests/files/npbind/notAFile.ext"), "^Error: Input path does not exist or is not a file! at \"npbind\\.cpp\":\\d*:\\(read\\)$", "Opened non-file object as file"); 27 | 28 | // Open file without permission to access 29 | EXPECT_EXCEPTION_REGEX(npbind::read("./tests/files/npbind/noPermission.ext"), "^Error: Cannot open file: \\./tests/files/npbind/noPermission\\.ext at \"npbind\\.cpp\":\\d*:\\(read\\)$", "Could \"open\" file without permissions"); 30 | 31 | // File is too small for a npbind.dat 32 | EXPECT_EXCEPTION_REGEX(npbind::read("./tests/files/npbind/brokenNpbindHeaderSize.dat"), "^Error: Error reading header! at \"npbind\\.cpp\":\\d*:\\(read\\)$", "Could not read header"); 33 | 34 | // File is not a npbind.dat 35 | EXPECT_EXCEPTION_REGEX(npbind::read("./tests/files/npbind/brokenNpbindMagic.dat"), "^Error: Input path is not a npbind.dat! at \"npbind\\.cpp\":\\d*:\\(read\\)$", "Passed file that wasn't a npbind.dat"); 36 | 37 | // Could not read entries 38 | EXPECT_EXCEPTION_REGEX(npbind::read("./tests/files/npbind/brokenNpbindEntriesSize.dat"), "^Error: Error reading entries! at \"npbind\\.cpp\":\\d*:\\(read\\)$", "Could not read entries"); 39 | 40 | // Non-matching digests 41 | EXPECT_EXCEPTION_REGEX(npbind::read("./tests/files/npbind/brokenNpbindDigest.dat"), "^Error: Digests do not match! Aborting... at \"npbind\\.cpp\":\\d*:\\(read\\)$", "Passed non matching digests"); 42 | 43 | // Success 44 | if (npbind::read("./tests/files/npbind/valid_NoEntry.dat").size() != 0) { 45 | FAIL() << "No entry file failed"; 46 | } 47 | 48 | if (npbind::read("./tests/files/npbind/valid_NoEntry_Alternative.dat").size() != 0) { // Includes entry size in file header 49 | FAIL() << "No entry (alternative) file failed"; 50 | } 51 | 52 | if (npbind::read("./tests/files/npbind/valid_OneEntry.dat").size() != 1) { 53 | FAIL() << "One entry entry file failed"; 54 | } 55 | 56 | if (npbind::read("./tests/files/npbind/valid_TwoEntries.dat").size() != 2) { 57 | FAIL() << "Two entry entry file failed"; 58 | } 59 | 60 | if (npbind::read("./tests/files/npbind/valid_ThreeEntries.dat").size() != 3) { 61 | FAIL() << "Three entry entry file failed"; 62 | } 63 | } 64 | 65 | #endif // DUMPER_TESTS_NPBIND_TEST_H_ 66 | -------------------------------------------------------------------------------- /tests/pfs_test.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Al Azif 2 | // License: GPLv3 3 | 4 | #ifndef DUMPER_TESTS_PFS_TEST_H_ 5 | #define DUMPER_TESTS_PFS_TEST_H_ 6 | 7 | #include "pfs.h" 8 | 9 | #include 10 | 11 | #include "testing.h" 12 | 13 | TEST(pfsTests, parseDirectory) { 14 | // TODO 15 | } 16 | 17 | TEST(pfsTests, calculatePfsSize) { 18 | // TODO 19 | } 20 | 21 | TEST(pfsTests, dumpPfs) { 22 | //TODO 23 | } 24 | 25 | TEST(pfsTests, extract) { 26 | // TODO 27 | } 28 | 29 | #endif // DUMPER_TESTS_PFS_TEST_H_ 30 | -------------------------------------------------------------------------------- /tests/sfo_test.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Al Azif 2 | // License: GPLv3 3 | 4 | #ifndef DUMPER_TESTS_SFO_TEST_H_ 5 | #define DUMPER_TESTS_SFO_TEST_H_ 6 | 7 | #include "sfo.h" 8 | 9 | #include 10 | 11 | #include "testing.h" 12 | 13 | TEST(sfoTests, isSfo) { 14 | // Empty input arguments 15 | EXPECT_EXCEPTION_REGEX(sfo::is_sfo(""), "^Error: Empty path argument! at \"sfo\\.cpp\":\\d*:\\(is_sfo\\)$", "Accepted empty argument"); // Empty 16 | EXPECT_EXCEPTION_REGEX(sfo::is_sfo(" "), "^Error: Empty path argument! at \"sfo\\.cpp\":\\d*:\\(is_sfo\\)$", "Accepted whitespace argument"); // Single space 17 | EXPECT_EXCEPTION_REGEX(sfo::is_sfo(" "), "^Error: Empty path argument! at \"sfo\\.cpp\":\\d*:\\(is_sfo\\)$", "Accepted whitespace argument"); // Double space 18 | EXPECT_EXCEPTION_REGEX(sfo::is_sfo("\t"), "^Error: Empty path argument! at \"sfo\\.cpp\":\\d*:\\(is_sfo\\)$", "Accepted whitespace argument"); // Single tab 19 | EXPECT_EXCEPTION_REGEX(sfo::is_sfo("\t\t"), "^Error: Empty path argument! at \"sfo\\.cpp\":\\d*:\\(is_sfo\\)$", "Accepted whitespace argument"); // Double tab 20 | EXPECT_EXCEPTION_REGEX(sfo::is_sfo(nullptr), "^basic_string::_M_construct null not valid$", "Accepted nullptr argument"); // nullptr 21 | 22 | // Non-existant file 23 | EXPECT_EXCEPTION_REGEX(sfo::is_sfo("./tests/files/sfo/doesNotExist.ext"), "^Error: Input path does not exist or is not a file! at \"sfo\\.cpp\":\\d*:\\(is_sfo\\)$", "Opened non-existant file"); 24 | 25 | // Open non-file object 26 | EXPECT_EXCEPTION_REGEX(sfo::is_sfo("./tests/files/sfo/notAFile.ext"), "^Error: Input path does not exist or is not a file! at \"sfo\\.cpp\":\\d*:\\(is_sfo\\)$", "Opened non-file object as file"); 27 | 28 | // Open file without permission to access 29 | EXPECT_EXCEPTION_REGEX(sfo::is_sfo("./tests/files/sfo/noPermission.ext"), "^Error: Cannot open file: \\./tests/files/sfo/noPermission\\.ext at \"sfo\\.cpp\":\\d*:\\(is_sfo\\)$", "Could \"open\" file without permissions"); 30 | 31 | /* TODO 32 | // False 33 | EXPECT_FALSE(sfo::is_sfo("./tests/files/sfo/brokenSfoSize.sfo")); 34 | EXPECT_FALSE(sfo::is_sfo("./tests/files/sfo/brokenSfoMagic.sfo")); 35 | 36 | // True 37 | EXPECT_TRUE(sfo::is_sfo("./tests/files/sfo/valid.sfo")); 38 | */ 39 | } 40 | 41 | TEST(sfoTests, read) { 42 | // TODO 43 | } 44 | 45 | TEST(sfoTests, getKeys) { 46 | // TODO 47 | } 48 | 49 | TEST(sfoTests, getFormat) { 50 | // TODO 51 | } 52 | 53 | TEST(sfoTests, getLength) { 54 | // TODO 55 | } 56 | 57 | TEST(sfoTests, getMaxLength) { 58 | // TODO 59 | } 60 | 61 | TEST(sfoTests, getValue) { 62 | // TODO 63 | } 64 | 65 | TEST(sfoTests, readPubtoolData) { 66 | // TODO 67 | } 68 | 69 | TEST(sfoTests, getPubtoolKeys) { 70 | // TODO 71 | } 72 | 73 | TEST(sfoTests, getPubtoolValue) { 74 | // TODO 75 | } 76 | 77 | TEST(sfoTests, buildData) { 78 | // TODO 79 | } 80 | 81 | TEST(sfoTests, buildPubtoolData) { 82 | // TODO 83 | } 84 | 85 | TEST(sfoTests, addData) { 86 | // TODO 87 | } 88 | 89 | TEST(sfoTests, addPubtoolData) { 90 | // TODO 91 | } 92 | 93 | TEST(sfoTests, removeKey) { 94 | // TODO 95 | } 96 | 97 | TEST(sfoTests, removePubtoolKey) { 98 | // TODO 99 | } 100 | 101 | TEST(sfoTests, compareSfoData) { 102 | // TODO 103 | } 104 | 105 | TEST(sfoTests, write) { 106 | // TODO 107 | } 108 | 109 | #endif // DUMPER_TESTS_SFO_TEST_H_ 110 | -------------------------------------------------------------------------------- /tests/testing.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Al Azif 2 | // License: GPLv3 3 | 4 | #ifndef DUMPER_TESTS_TESTING_H_ 5 | #define DUMPER_TESTS_TESTING_H_ 6 | 7 | #include "common.h" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #if defined(__ORBIS__) 15 | #include 16 | #else 17 | #include 18 | #endif // __ORBIS__ 19 | 20 | // Checks the expression to throw a specific exception message matching a regex string 21 | // If the test doesn't thow an exception the test fails and "pass" is printed 22 | #define EXPECT_EXCEPTION_REGEX(expression, exception_string, pass) \ 23 | { \ 24 | try { \ 25 | expression; \ 26 | FAIL() << pass; \ 27 | } catch (const std::exception &err) { \ 28 | if (!std::regex_match(err.what(), std::regex(exception_string))) { \ 29 | FAIL() << "Unexpected exception"; \ 30 | } \ 31 | } \ 32 | } 33 | 34 | #if defined(__ORBIS__) 35 | #define SHA256SUM(file_path, checksum) \ 36 | { \ 37 | std::ifstream file_to_check(file_path, std::ios::in | std::ios::binary); \ 38 | if (!file_to_check || !file_to_check.good()) { \ 39 | file_to_check.close(); \ 40 | FAIL() << "Cannot open decrypted file: " << file_path; \ 41 | } \ 42 | std::string checksum_string(checksum); \ 43 | unsigned char digest[SHA256_DIGEST_LENGTH]; \ 44 | size_t count = checksum_string.size() / 2; \ 45 | for (size_t i = 0; count > i; i++) { \ 46 | uint32_t s = 0; \ 47 | std::stringstream ss; \ 48 | ss << std::hex << checksum_string.substr(i * 2, 2); \ 49 | ss >> s; \ 50 | digest[i] = static_cast(s); \ 51 | } \ 52 | unsigned char calculated_digest[SHA256_DIGEST_LENGTH]; \ 53 | SceSha256Context context; \ 54 | sceSha256BlockInit(&context); \ 55 | while (file_to_check.good()) { \ 56 | unsigned char buffer[PAGE_SIZE]; \ 57 | file_to_check.read((char *)buffer, sizeof(buffer)); /* Flawfinder: ignore */ \ 58 | sceSha256BlockUpdate(&context, buffer, decrypted_elf.gcount()); \ 59 | } \ 60 | file_to_check.close(); \ 61 | sceSha256BlockResult(calculated_digest, &context)); \ 62 | if (std::memcmp(calculated_digest, digest, sizeof(SHA256_DIGEST_LENGTH)) != 0) { \ 63 | FAIL() << "Checksum does not match!"; \ 64 | } \ 65 | } 66 | #else 67 | #define SHA256SUM(file_path, checksum) \ 68 | { \ 69 | std::ifstream file_to_check(file_path, std::ios::in | std::ios::binary); \ 70 | if (!file_to_check || !file_to_check.good()) { \ 71 | file_to_check.close(); \ 72 | FAIL() << "Cannot open decrypted file: " << file_path; \ 73 | } \ 74 | std::string checksum_string(checksum); \ 75 | unsigned char digest[SHA256_DIGEST_LENGTH]; \ 76 | size_t count = checksum_string.size() / 2; \ 77 | for (size_t i = 0; count > i; i++) { \ 78 | uint32_t s = 0; \ 79 | std::stringstream ss; \ 80 | ss << std::hex << checksum_string.substr(i * 2, 2); \ 81 | ss >> s; \ 82 | digest[i] = static_cast(s); \ 83 | } \ 84 | unsigned char calculated_digest[SHA256_DIGEST_LENGTH]; \ 85 | SHA256_CTX context; \ 86 | SHA256_Init(&context); \ 87 | while (file_to_check.good()) { \ 88 | unsigned char buffer[PAGE_SIZE]; \ 89 | file_to_check.read((char *)buffer, sizeof(buffer)); /* Flawfinder: ignore */ \ 90 | SHA256_Update(&context, buffer, file_to_check.gcount()); \ 91 | } \ 92 | file_to_check.close(); \ 93 | SHA256_Final(calculated_digest, &context); \ 94 | if (std::memcmp(calculated_digest, digest, sizeof(SHA256_DIGEST_LENGTH)) != 0) { \ 95 | FAIL() << "Checksum does not match!"; \ 96 | } \ 97 | } 98 | #endif // __ORBIS__ 99 | 100 | #endif // DUMPER_TESTS_TESTING_H_ 101 | --------------------------------------------------------------------------------