#pragma once #include #include #include #include #include #include #include #include #include namespace FileFormat { using namespace boost::endian; // Strong typedef of the given argument type to represent a media unit (0x200 bytes) template struct MediaUnit { BOOST_HANA_DEFINE_STRUCT(MediaUnit, (T, raw) ); // TODO: Static assert this is representable uint64_t ToBytes() const { return uint64_t{raw} * 0x200; } static MediaUnit FromBytes(uint64_t bytes) { return { static_cast(bytes / 0x200) }; } }; using MediaUnit32 = MediaUnit; using Bytes32 = uint32_t; struct NCCHHeader { BOOST_HANA_DEFINE_STRUCT(NCCHHeader, (std::array, signature), // RSA-2048 signature (std::array, magic), // always "NCCH" (MediaUnit32, content_size), // total size of this file (uint64_t, partition_id), // Always the same as the title id? (std::array, unknown), (uint64_t, program_id), // Always the same as the title id? (std::array, unknown2a), // Only since system version 5.0 (TODO: Is there some indicator for the presence of this field?) (std::array, logo_sha256), (std::array, unknown2b), (std::array, exheader_sha256), // Reduced size of the extended header: Only refers to System Control Info and Access Control Info (Bytes32, exheader_size), (std::array, unknown3), // If non-zero, an additional layer of encryption is used for RomFS and parts of ExeFS (uint8_t, crypto_method), // 1 = Old3DS, 2 = New3DS (uint8_t, platform), // 1 = data, 2 = executable, 4 = system update, 8 = manual, 0x10 = trial (uint8_t, type_mask), // logarithmic unit size in MediaUnits: unit_size_bytes = 0x200 * 2^unit_size_log2 (uint8_t, unit_size_log2), // 1 = fixed encryption key, 2 = don't mount the romfs, 4 = no content encryption, 0x20 = use 9.6.0 keyY generator (uint8_t, flags), (MediaUnit32, plain_data_offset), // Unencrypted plain data (MediaUnit32, plain_data_size), (MediaUnit32, logo_offset), // logo (introduced in 5.0.0, it seems?) (MediaUnit32, logo_size), (MediaUnit32, exefs_offset), // offset from the start of this file (MediaUnit32, exefs_size), (MediaUnit32, exefs_hash_message_size), (std::array, unknown4), (MediaUnit32, romfs_offset), // offset from the start of this file (MediaUnit32, romfs_size), (MediaUnit32, romfs_hash_message_size), (std::array, unknown5), (std::array, exefs_sha256), (std::array, romfs_sha256) ); struct Tags : expected_size_tag<0x200> {}; // Followed by: // ExHeader (optional?); 3dbrew erratum: it seems the "access descriptor" that was documented to follow the exheader is already part of this exheader // plaintext binary region (optional, CXI only) // plaintext logo region (optional, CXI only) // ExeFS (optional) // RomFS (optional) }; // Extended Header struct ExHeader { struct CodeSetInfo { BOOST_HANA_DEFINE_STRUCT(CodeSetInfo, (uint32_t, address), (uint32_t, size_pages), (uint32_t, size_bytes) ); }; struct Flags { BOOST_HANA_DEFINE_STRUCT(Flags, (uint8_t, storage) ); using Fields = v2::BitField::Fields; /// If set, the ExeFS:/.code section is compressed with a LZ77 variant auto compress_exefs_code() const { return Fields::MakeOn<0, 1>(this); } }; /// Flags for AccessControlInfo struct ACIFlags { BOOST_HANA_DEFINE_STRUCT(ACIFlags, (uint32_t, storage) ); using Fields = v2::BitField::Fields; /** * @note flag1 bits in the primary ACI may be set only if the secondary ACI has them set */ auto flag1() const { return Fields::MakeOn<0, 8>(this); } auto flag2() const { return Fields::MakeOn<8, 8>(this); } auto flag0() const { return Fields::MakeOn<16, 8>(this); } auto ideal_processor() const { return Fields::MakeOn<16, 2>(this); } /** * Thread priority * @todo Figure out whether this is the "default" thread priority * or just the priority of the main thread */ auto priority() const { return Fields::MakeOn<24, 8>(this); } }; /** * Descriptor for some kernel capability: There are a number of different * descriptor types, each of which has their own structure and fields. * The descriptor type is determined by the number of leading ones in * the bit pattern of the raw descriptor value. * * We expose this structure using a visitor pattern: The visit() method * checks the descriptor type according to the raw storage value, * constructs an appropriate strongly-typed descriptor (see children of the * DescriptorTypeBase structure), and calls (an appropriate overload of) * the given function object with this descriptor. */ struct ARM11KernelCapabilityDescriptor { BOOST_HANA_DEFINE_STRUCT(ARM11KernelCapabilityDescriptor, (uint32_t, storage) ); using Fields = v2::BitField::Fields; template struct DescriptorTypeBase { uint32_t storage; static_assert(IdentifierPos < 32, "Invalid identifying field position: Must be smaller than 32"); constexpr auto id_field() const { return BitField::v3::MakeFieldOn(static_cast(this)); } static constexpr auto id_field_ref() { return CRTP{}.id_field().MaxValue() - 1; } static CRTP Make() { return CRTP{}.id_field().Set(id_field_ref()); } operator ARM11KernelCapabilityDescriptor() const { return { storage }; } }; /// A group of these make up a mask of SVCs that are accessible to the application struct SVCMaskTableEntry : DescriptorTypeBase { auto entry_index() const { return Fields::MakeOn<24, 3>(this); } auto mask() const { return Fields::MakeOn<0, 24>(this); } }; struct HandleTableInfo : DescriptorTypeBase { // TODO: Verify this is given in terms of handles rather than bytes auto num_handles() const { return Fields::MakeOn<0, 19>(this); } }; struct MappedMemoryPage : DescriptorTypeBase { auto page_index() const { return Fields::MakeOn<0, 20>(this); } }; struct MappedMemoryRange : DescriptorTypeBase { // TODO: What about bit 21 within storage? Does it need always need to be 0? (it does according to 3dbrew!) auto page_index() const { return Fields::MakeOn<0, 20>(this); } // TODO: Verify this field... auto read_only() const { return Fields::MakeOn<20, 1>(this); } }; struct KernelFlags : DescriptorTypeBase { /// Allow this application to be debugged (TODO: Verify) auto allow_debug() const { return Fields::MakeOn<0, 1>(this); } /// Force other applications to be debugged even if they don't have "allow_debug" set (TODO: Verify) auto force_debug() const { return Fields::MakeOn<1, 1>(this); } /// "Allow non-alphanum" according to 3dbrew? auto unknown2() const { return Fields::MakeOn<2, 1>(this); } /// If set, the shared page will be mapped with write-permissions into this process auto shared_page_writeable() const { return Fields::MakeOn<3, 1>(this); } /// "privilege priority" according to 3dbrew? auto unknown4() const { return Fields::MakeOn<4, 1>(this); } /// "allow main args" according to 3dbrew? auto unknown5() const { return Fields::MakeOn<5, 1>(this); } /// "shared device memory" according to 3dbrew? auto unknown6() const { return Fields::MakeOn<6, 1>(this); } /// "Runnable on sleep" according to 3dbrew? auto unknown7() const { return Fields::MakeOn<7, 1>(this); } /** * System/Memory/Base * @todo Verify that this does really occupy 4 bits * @todo Use the kernel MemoryType enum for this */ auto memory_type() const { return Fields::MakeOn<8, 4>(this); } /** * If set, the application code is mapped to a virtual memory * address specified in the exheader. Otherwise, the standard * starting address 0x00100000 is used. */ auto nonstandard_code_address() const { return Fields::MakeOn<12, 1>(this); } /// Enable access to the second CPU core (New3DS only) auto enable_second_cpu_core() const { return Fields::MakeOn<13, 1>(this); } }; template struct CheckDescriptorType { uint32_t descriptor; F& visitor; /// Set to true if we found at least one match bool match_found = false; template void operator()(boost::mp11::mp_identity) { T typed_descriptor = { descriptor }; auto actual_field = typed_descriptor.id_field()(); if (actual_field != typed_descriptor.id_field_ref()) return; match_found = true; T ret; ret.storage = descriptor; visitor(ret); } }; /** * Determines the type of this descriptor, constructs a strongly-typed * variant of it (see children of DescriptorTypeBase), and calls the * given visitor with it. * * If no known strongly-typed descriptor exists (or the raw descriptor * is just an invalid descriptor in the first place), the visitor is * called with the raw uint32_t descriptor value. */ template auto visit(F&& visitor) { using namespace boost::mp11; // TODO: Order these by increasing number of leading zeroes using all_types = mp_transform>; auto checker = CheckDescriptorType{storage, visitor}; auto checker2 = boost::mp11::mp_for_each(checker); if (!checker2.match_found) return visitor(storage); } }; struct AccessControlInfo { BOOST_HANA_DEFINE_STRUCT(AccessControlInfo, // "ARM11 Local System Capabilities" (uint64_t, program_id), // Checked against the lower word in the FIRM title id? (uint32_t, version), (ACIFlags, flags), // Resource limit descriptors + storage info (std::array, unknown5), (std::array, 0x20>, service_access_list), (std::array, unknown6), (std::array, arm11_kernel_capabilities), // "ARM9 access control" (std::array, unknown7) ); struct Tags : expected_size_tag<0x200> {}; }; BOOST_HANA_DEFINE_STRUCT(ExHeader, // Begin of "System Control Info" (std::array, application_title), (std::array, unknown), (Flags, flags), (uint16_t, remaster_version), // ??? (CodeSetInfo, section_text), (uint32_t, stack_size), (CodeSetInfo, section_ro), (std::array, unknown3), // data section information is exclusive of bss (CodeSetInfo, section_data), (uint32_t, bss_size), // Title IDs of dependencies (0 if entry unused) (std::array, dependencies), (uint64_t, save_data_size), (uint64_t, jump_id), // Seems to be some sort of title id??? (std::array, unknown4), // End of "System Control Info" /** * Primary ACI. Specifies the actual parameters that are used for * access control, while the secondary ACI (see below) restricts the * set of valid parameters specified in the primary one. This scheme * allows game developers to choose what privileges to give to their * applications while keeping the console vendor in control over the * maximum set of privileges they can possibly have. */ (AccessControlInfo, aci), /// RSA2048-SHA256 signature covering ncch_sig_pub_key and aci_limits (std::array, subsignature), /// Public key for use in NCCH RSA2048-SHA256 signatures (std::array, ncch_sig_pub_key), /** * The secondary ACI, signed by Nintendo (signature given above) as a * limitation to the first ACI; if the first ACI does not match the * contents of the second, the system will refuse to boot this title. * * For retail applications, there is not much point in this since the * entity who signed the ExHeader will likely be the same as the one * who signed the second ACI. However, for debug unit builds (where * game developers can self-sign their titles), the second ACI can * not be changed without breaking the signature, hence it keeps * developers from requesting privileges for the application that * they should not be using on retail units. * * @todo Figure out and document what specifically "limitation" means here */ (AccessControlInfo, aci_limits) ); struct Tags : expected_size_tag<0x800> {}; }; struct ExeFSHeader { struct FileHeader { BOOST_HANA_DEFINE_STRUCT(FileHeader, (std::array, name), // ".code", "logo", "banner", or "icon" (uint32_t, offset), // byte offset starting after the ExeFS header (uint32_t, size_bytes) ); }; BOOST_HANA_DEFINE_STRUCT(ExeFSHeader, // TODO: Confirm that 10 files is indeed the maximum (std::array, files), (std::array, unknown), // Hashes for each ExeFS file; stored in reverse order, i.e. the last hashes element corresponds to the first file (std::array, 10>, hashes) ); uint64_t GetExeFSSize() const { using ranges::v3::max; using ranges::v3::view::transform; auto end_offset = [](auto& file) { return file.offset + file.size_bytes; }; // TODO: Don't hardcode the header size! return 0x200 + max(files | transform(end_offset)); } struct Tags : expected_size_tag<0x200> {}; static constexpr const char code_section_name[8] = ".code\0\0"; }; struct RomFSLevel3Header { BOOST_HANA_DEFINE_STRUCT(RomFSLevel3Header, (uint32_t, header_size), // in bytes // Offsets and sizes are measured in bytes. // Offsets are all from start of this header. (uint32_t, dir_hash_offset), (uint32_t, dir_hash_size), (uint32_t, dir_metadata_offset), (uint32_t, dir_metadata_size), (uint32_t, file_hash_offset), (uint32_t, file_hash_size), (uint32_t, file_metadata_offset), (uint32_t, file_metadata_size), (uint32_t, file_data_offset) ); struct Tags : expected_size_tag<0x28> {}; }; struct RomFSFileMetadata { BOOST_HANA_DEFINE_STRUCT(RomFSFileMetadata, // Offsets and sizes are all measured in bytes. (uint32_t, parent_dir_index), // index within directory metadata table (uint32_t, unknown2), (uint64_t, data_offset), // from start of file data section (uint64_t, data_size), (uint32_t, unknown5), (uint32_t, name_size) // length of name in bytes (NOTE: Actual filename data size is aligned to 4 bytes) ); struct Tags : expected_size_tag<0x20> {}; }; struct NCSDHeader { struct Partition { BOOST_HANA_DEFINE_STRUCT(Partition, (MediaUnit32, offset), // from start of file (MediaUnit32, size) ); }; BOOST_HANA_DEFINE_STRUCT(NCSDHeader, (std::array, signature), // RSA-2048 SHA-256 signature of this header (starting from magic?) (std::array, magic), // always "NCSD" (MediaUnit32, size), // total NCSD size (std::array, unknown), (std::array, partition_table), (std::array, unknown2) ); struct Tags : expected_size_tag<0x200> {}; }; } // namespace FileFormat