diff options
| author | 2014-06-16 22:57:09 -0400 | |
|---|---|---|
| committer | 2014-06-16 23:43:29 -0400 | |
| commit | 3577dd027ddbaa2517eacad700ed0768a1f4b518 (patch) | |
| tree | 4ddf41f874385673ed1e1e1396b3731e67810318 /src/core/loader/ncch.cpp | |
| parent | Loader: Moved elf and loader modules to a "loader" subdirectory. (diff) | |
| download | yuzu-3577dd027ddbaa2517eacad700ed0768a1f4b518.tar.gz yuzu-3577dd027ddbaa2517eacad700ed0768a1f4b518.tar.xz yuzu-3577dd027ddbaa2517eacad700ed0768a1f4b518.zip | |
Loader: Added support for booting NCCH executables.
NCCH: Fixed typo in printing NCCH filename.
Diffstat (limited to 'src/core/loader/ncch.cpp')
| -rw-r--r-- | src/core/loader/ncch.cpp | 348 |
1 files changed, 348 insertions, 0 deletions
diff --git a/src/core/loader/ncch.cpp b/src/core/loader/ncch.cpp new file mode 100644 index 000000000..a01411e11 --- /dev/null +++ b/src/core/loader/ncch.cpp | |||
| @@ -0,0 +1,348 @@ | |||
| 1 | // Copyright 2014 Citra Emulator Project | ||
| 2 | // Licensed under GPLv2 | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #include "common/file_util.h" | ||
| 6 | |||
| 7 | #include "core/loader/ncch.h" | ||
| 8 | #include "core/hle/kernel/kernel.h" | ||
| 9 | #include "core/mem_map.h" | ||
| 10 | |||
| 11 | //////////////////////////////////////////////////////////////////////////////////////////////////// | ||
| 12 | /// NCCH header (Note: "NCCH" appears to be a publically unknown acronym) | ||
| 13 | |||
| 14 | struct NCCH_Header { | ||
| 15 | u8 signature[0x100]; | ||
| 16 | char magic[4]; | ||
| 17 | u32 content_size; | ||
| 18 | u8 partition_id[8]; | ||
| 19 | u16 maker_code; | ||
| 20 | u16 version; | ||
| 21 | u8 reserved_0[4]; | ||
| 22 | u8 program_id[8]; | ||
| 23 | u8 temp_flag; | ||
| 24 | u8 reserved_1[0x2f]; | ||
| 25 | u8 product_code[0x10]; | ||
| 26 | u8 extended_header_hash[0x20]; | ||
| 27 | u32 extended_header_size; | ||
| 28 | u8 reserved_2[4]; | ||
| 29 | u8 flags[8]; | ||
| 30 | u32 plain_region_offset; | ||
| 31 | u32 plain_region_size; | ||
| 32 | u8 reserved_3[8]; | ||
| 33 | u32 exefs_offset; | ||
| 34 | u32 exefs_size; | ||
| 35 | u32 exefs_hash_region_size; | ||
| 36 | u8 reserved_4[4]; | ||
| 37 | u32 romfs_offset; | ||
| 38 | u32 romfs_size; | ||
| 39 | u32 romfs_hash_region_size; | ||
| 40 | u8 reserved_5[4]; | ||
| 41 | u8 exefs_super_block_hash[0x20]; | ||
| 42 | u8 romfs_super_block_hash[0x20]; | ||
| 43 | }; | ||
| 44 | |||
| 45 | //////////////////////////////////////////////////////////////////////////////////////////////////// | ||
| 46 | // ExeFS (executable file system) headers | ||
| 47 | |||
| 48 | typedef struct { | ||
| 49 | char name[8]; | ||
| 50 | u32 offset; | ||
| 51 | u32 size; | ||
| 52 | } ExeFs_SectionHeader; | ||
| 53 | |||
| 54 | typedef struct { | ||
| 55 | ExeFs_SectionHeader section[8]; | ||
| 56 | u8 reserved[0x80]; | ||
| 57 | u8 hashes[8][0x20]; | ||
| 58 | } ExeFs_Header; | ||
| 59 | |||
| 60 | //////////////////////////////////////////////////////////////////////////////////////////////////// | ||
| 61 | // ExHeader (executable file system header) headers | ||
| 62 | |||
| 63 | struct ExHeader_SystemInfoFlags{ | ||
| 64 | u8 reserved[5]; | ||
| 65 | u8 flag; | ||
| 66 | u8 remaster_version[2]; | ||
| 67 | } exheader_systeminfoflags; | ||
| 68 | |||
| 69 | struct ExHeader_CodeSegmentInfo{ | ||
| 70 | u32 address; | ||
| 71 | u32 num_max_pages; | ||
| 72 | u32 code_size; | ||
| 73 | } exheader_codesegmentinfo; | ||
| 74 | |||
| 75 | struct ExHeader_CodeSetInfo { | ||
| 76 | u8 name[8]; | ||
| 77 | ExHeader_SystemInfoFlags flags; | ||
| 78 | ExHeader_CodeSegmentInfo text; | ||
| 79 | u8 stacksize[4]; | ||
| 80 | ExHeader_CodeSegmentInfo ro; | ||
| 81 | u8 reserved[4]; | ||
| 82 | ExHeader_CodeSegmentInfo data; | ||
| 83 | u8 bsssize[4]; | ||
| 84 | }; | ||
| 85 | |||
| 86 | struct ExHeader_DependencyList{ | ||
| 87 | u8 program_id[0x30][8]; | ||
| 88 | }; | ||
| 89 | |||
| 90 | struct ExHeader_SystemInfo{ | ||
| 91 | u32 save_data_size; | ||
| 92 | u8 reserved[4]; | ||
| 93 | u8 jump_id[8]; | ||
| 94 | u8 reserved_2[0x30]; | ||
| 95 | }; | ||
| 96 | |||
| 97 | struct ExHeader_StorageInfo{ | ||
| 98 | u8 ext_save_data_id[8]; | ||
| 99 | u8 system_save_data_id[8]; | ||
| 100 | u8 reserved[8]; | ||
| 101 | u8 access_info[7]; | ||
| 102 | u8 other_attributes; | ||
| 103 | }; | ||
| 104 | |||
| 105 | struct ExHeader_ARM11_SystemLocalCaps{ | ||
| 106 | u8 program_id[8]; | ||
| 107 | u8 flags[8]; | ||
| 108 | u8 resource_limit_descriptor[0x10][2]; | ||
| 109 | ExHeader_StorageInfo storage_info; | ||
| 110 | u8 service_access_control[0x20][8]; | ||
| 111 | u8 reserved[0x1f]; | ||
| 112 | u8 resource_limit_category; | ||
| 113 | }; | ||
| 114 | |||
| 115 | struct ExHeader_ARM11_KernelCaps{ | ||
| 116 | u8 descriptors[28][4]; | ||
| 117 | u8 reserved[0x10]; | ||
| 118 | }; | ||
| 119 | |||
| 120 | struct ExHeader_ARM9_AccessControl{ | ||
| 121 | u8 descriptors[15]; | ||
| 122 | u8 descversion; | ||
| 123 | }; | ||
| 124 | |||
| 125 | struct ExHeader_Header{ | ||
| 126 | ExHeader_CodeSetInfo codeset_info; | ||
| 127 | ExHeader_DependencyList dependency_list; | ||
| 128 | ExHeader_SystemInfo system_info; | ||
| 129 | ExHeader_ARM11_SystemLocalCaps arm11_system_local_caps; | ||
| 130 | ExHeader_ARM11_KernelCaps arm11_kernel_caps; | ||
| 131 | ExHeader_ARM9_AccessControl arm9_access_control; | ||
| 132 | struct { | ||
| 133 | u8 signature[0x100]; | ||
| 134 | u8 ncch_public_key_modulus[0x100]; | ||
| 135 | ExHeader_ARM11_SystemLocalCaps arm11_system_local_caps; | ||
| 136 | ExHeader_ARM11_KernelCaps arm11_kernel_caps; | ||
| 137 | ExHeader_ARM9_AccessControl arm9_access_control; | ||
| 138 | } access_desc; | ||
| 139 | }; | ||
| 140 | |||
| 141 | //////////////////////////////////////////////////////////////////////////////////////////////////// | ||
| 142 | // Loader namespace | ||
| 143 | |||
| 144 | namespace Loader { | ||
| 145 | |||
| 146 | const int kExeFs_MaxSections = 8; ///< Maximum number of sections (files) in an ExeFs | ||
| 147 | const int kExeFs_BlockSize = 0x200; ///< Size of ExeFS blocks (in bytes) | ||
| 148 | |||
| 149 | /** | ||
| 150 | * Get the decompressed size of an LZSS compressed ExeFS file | ||
| 151 | * @param buffer Buffer of compressed file | ||
| 152 | * @param size Size of compressed buffer | ||
| 153 | * @return Size of decompressed buffer | ||
| 154 | */ | ||
| 155 | u32 LZSS_GetDecompressedSize(u8* buffer, u32 size) { | ||
| 156 | u32 offset_size = *(u32*)(buffer + size - 4); | ||
| 157 | return offset_size + size; | ||
| 158 | } | ||
| 159 | |||
| 160 | /** | ||
| 161 | * Decompress ExeFS file (compressed with LZSS) | ||
| 162 | * @param compressed Compressed buffer | ||
| 163 | * @param compressed_size Size of compressed buffer | ||
| 164 | * @param decompressed Decompressed buffer | ||
| 165 | * @param decompressed_size Size of decompressed buffer | ||
| 166 | * @param error_string String populated with error message on failure | ||
| 167 | * @return True on success, otherwise false | ||
| 168 | */ | ||
| 169 | bool LZSS_Decompress(u8* compressed, u32 compressed_size, u8* decompressed, u32 decompressed_size, | ||
| 170 | std::string* error_string) { | ||
| 171 | u8* footer = compressed + compressed_size - 8; | ||
| 172 | u32 buffer_top_and_bottom = *(u32*)footer; | ||
| 173 | u32 i, j; | ||
| 174 | u32 out = decompressed_size; | ||
| 175 | u32 index = compressed_size - ((buffer_top_and_bottom >> 24) & 0xFF); | ||
| 176 | u8 control; | ||
| 177 | u32 stop_index = compressed_size - (buffer_top_and_bottom & 0xFFFFFF); | ||
| 178 | |||
| 179 | memset(decompressed, 0, decompressed_size); | ||
| 180 | memcpy(decompressed, compressed, compressed_size); | ||
| 181 | |||
| 182 | while(index > stop_index) { | ||
| 183 | control = compressed[--index]; | ||
| 184 | |||
| 185 | for(i = 0; i < 8; i++) { | ||
| 186 | if(index <= stop_index) | ||
| 187 | break; | ||
| 188 | if(index <= 0) | ||
| 189 | break; | ||
| 190 | if(out <= 0) | ||
| 191 | break; | ||
| 192 | |||
| 193 | if(control & 0x80) { | ||
| 194 | if(index < 2) { | ||
| 195 | *error_string = "Compression out of bounds"; | ||
| 196 | return false; | ||
| 197 | } | ||
| 198 | index -= 2; | ||
| 199 | |||
| 200 | u32 segment_offset = compressed[index] | (compressed[index + 1] << 8); | ||
| 201 | u32 segment_size = ((segment_offset >> 12) & 15) + 3; | ||
| 202 | segment_offset &= 0x0FFF; | ||
| 203 | segment_offset += 2; | ||
| 204 | |||
| 205 | if(out < segment_size) { | ||
| 206 | *error_string = "Compression out of bounds"; | ||
| 207 | return false; | ||
| 208 | } | ||
| 209 | for(j = 0; j < segment_size; j++) { | ||
| 210 | u8 data; | ||
| 211 | if(out + segment_offset >= decompressed_size) { | ||
| 212 | *error_string = "Compression out of bounds"; | ||
| 213 | return false; | ||
| 214 | } | ||
| 215 | data = decompressed[out + segment_offset]; | ||
| 216 | decompressed[--out] = data; | ||
| 217 | } | ||
| 218 | } else { | ||
| 219 | if(out < 1) { | ||
| 220 | *error_string = "Compression out of bounds"; | ||
| 221 | return false; | ||
| 222 | } | ||
| 223 | decompressed[--out] = compressed[--index]; | ||
| 224 | } | ||
| 225 | control <<= 1; | ||
| 226 | } | ||
| 227 | } | ||
| 228 | return true; | ||
| 229 | } | ||
| 230 | |||
| 231 | /** | ||
| 232 | * Load a data buffer into memory at the specified address | ||
| 233 | * @param addr Address to load memory into | ||
| 234 | * @param buffer Buffer of data to load into memory | ||
| 235 | * @param size Size of data to load into memory | ||
| 236 | * @todo Perhaps move this code somewhere more generic? | ||
| 237 | */ | ||
| 238 | void LoadBuffer(const u32 addr, const u8* const buffer, const int size) { | ||
| 239 | u32 *dst = (u32*)Memory::GetPointer(addr); | ||
| 240 | u32 *src = (u32*)buffer; | ||
| 241 | int size_aligned = (size + 3) / 4; | ||
| 242 | |||
| 243 | for (int j = 0; j < size_aligned; j++) { | ||
| 244 | *dst++ = (*src++); | ||
| 245 | } | ||
| 246 | return; | ||
| 247 | } | ||
| 248 | |||
| 249 | /** | ||
| 250 | * Loads an NCCH file (e.g. from a CCI, or the first NCCH in a CXI) | ||
| 251 | * @param filename String filename of NCCH file | ||
| 252 | * @param error_string Pointer to string to put error message if an error has occurred | ||
| 253 | * @todo Move NCSD parsing out of here and create a separate function for loading these | ||
| 254 | * @return True on success, otherwise false | ||
| 255 | */ | ||
| 256 | bool Load_NCCH(std::string& filename, std::string* error_string) { | ||
| 257 | INFO_LOG(LOADER, "Loading NCCH file %s...", filename.c_str()); | ||
| 258 | |||
| 259 | File::IOFile file(filename, "rb"); | ||
| 260 | |||
| 261 | if (file.IsOpen()) { | ||
| 262 | NCCH_Header ncch_header; | ||
| 263 | file.ReadBytes(&ncch_header, sizeof(NCCH_Header)); | ||
| 264 | |||
| 265 | // Skip NCSD header and load first NCCH (NCSD is just a container of NCCH files)... | ||
| 266 | int ncch_off = 0; // Offset to NCCH header, can be 0 or after NCSD header | ||
| 267 | if (memcmp(&ncch_header.magic, "NCSD", 4) == 0) { | ||
| 268 | WARN_LOG(LOADER, "Only loading the first (bootable) NCCH within the NCSD file!"); | ||
| 269 | ncch_off = 0x4000; | ||
| 270 | file.Seek(ncch_off, 0); | ||
| 271 | file.ReadBytes(&ncch_header, sizeof(NCCH_Header)); | ||
| 272 | } | ||
| 273 | // Verify we are loading the correct file type... | ||
| 274 | if (memcmp(&ncch_header.magic, "NCCH", 4) != 0) { | ||
| 275 | *error_string = "Invalid NCCH magic number (likely incorrect file type)"; | ||
| 276 | return false; | ||
| 277 | } | ||
| 278 | // Read ExHeader | ||
| 279 | ExHeader_Header exheader_header; | ||
| 280 | file.ReadBytes(&exheader_header, sizeof(ExHeader_Header)); | ||
| 281 | |||
| 282 | bool is_compressed = (exheader_header.codeset_info.flags.flag & 1) == 1; | ||
| 283 | |||
| 284 | INFO_LOG(LOADER, "Name: %s", exheader_header.codeset_info.name); | ||
| 285 | INFO_LOG(LOADER, "Code compressed: %s", is_compressed ? "yes" : "no"); | ||
| 286 | |||
| 287 | // Read ExeFS | ||
| 288 | u32 exefs_offset = ncch_header.exefs_offset * kExeFs_BlockSize; | ||
| 289 | u32 exefs_size = ncch_header.exefs_size * kExeFs_BlockSize; | ||
| 290 | |||
| 291 | INFO_LOG(LOADER, "ExeFS offset: 0x%08X", exefs_offset); | ||
| 292 | INFO_LOG(LOADER, "ExeFS size: 0x%08X", exefs_size); | ||
| 293 | |||
| 294 | ExeFs_Header exefs_header; | ||
| 295 | file.Seek(exefs_offset + ncch_off, 0); | ||
| 296 | file.ReadBytes(&exefs_header, sizeof(ExeFs_Header)); | ||
| 297 | |||
| 298 | // Iterate through the ExeFs archive until we find the .code file... | ||
| 299 | for (int i = 0; i < kExeFs_MaxSections; i++) { | ||
| 300 | INFO_LOG(LOADER, "ExeFS section %d:", i); | ||
| 301 | INFO_LOG(LOADER, " name: %s", exefs_header.section[i].name); | ||
| 302 | INFO_LOG(LOADER, " offset: 0x%08X", exefs_header.section[i].offset); | ||
| 303 | INFO_LOG(LOADER, " size: 0x%08X", exefs_header.section[i].size); | ||
| 304 | |||
| 305 | // Load the .code section (executable code)... | ||
| 306 | if (strcmp((char*) exefs_header.section[i].name, ".code") == 0) { | ||
| 307 | file.Seek(exefs_header.section[i].offset + exefs_offset + sizeof(ExeFs_Header) + | ||
| 308 | ncch_off, 0); | ||
| 309 | |||
| 310 | u8* buffer = new u8[exefs_header.section[i].size]; | ||
| 311 | file.ReadBytes(buffer, exefs_header.section[i].size); | ||
| 312 | |||
| 313 | // Load compressed executable... | ||
| 314 | if (i == 0 && is_compressed) { | ||
| 315 | u32 decompressed_size = LZSS_GetDecompressedSize(buffer, | ||
| 316 | exefs_header.section[i].size); | ||
| 317 | u8* decompressed_buffer = new u8[decompressed_size]; | ||
| 318 | |||
| 319 | if (!LZSS_Decompress(buffer, exefs_header.section[i].size, decompressed_buffer, | ||
| 320 | decompressed_size, error_string)) { | ||
| 321 | return false; | ||
| 322 | } | ||
| 323 | // Load .code section into memory... | ||
| 324 | LoadBuffer(exheader_header.codeset_info.text.address, decompressed_buffer, | ||
| 325 | decompressed_size); | ||
| 326 | |||
| 327 | delete[] decompressed_buffer; | ||
| 328 | |||
| 329 | // Load uncompressed executable... | ||
| 330 | } else { | ||
| 331 | // Load .code section into memory... | ||
| 332 | LoadBuffer(exheader_header.codeset_info.text.address, buffer, | ||
| 333 | exefs_header.section[i].size); | ||
| 334 | } | ||
| 335 | delete[] buffer; | ||
| 336 | |||
| 337 | // Setup kernel emulation to boot .code section... | ||
| 338 | Kernel::LoadExec(exheader_header.codeset_info.text.address); | ||
| 339 | |||
| 340 | // No need to load the other files from ExeFS until we do something with them... | ||
| 341 | return true; | ||
| 342 | } | ||
| 343 | } | ||
| 344 | } | ||
| 345 | return false; | ||
| 346 | } | ||
| 347 | |||
| 348 | } // namespace Loader | ||