/* * VAS_EBOOT -- GRand Unified Bootloader * Copyright (C) 2012 Free Software Foundation, Inc. * * VAS_EBOOT is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * VAS_EBOOT is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with VAS_EBOOT. If not, see . */ /* HFS+ is documented at http://developer.apple.com/technotes/tn/tn1150.html */ #include #include #include #include #include #include VAS_EBOOT_MOD_LICENSE ("GPLv3+"); /* big-endian. */ struct VasEBoot_hfsplus_compress_header1 { VasEBoot_uint32_t header_size; VasEBoot_uint32_t end_descriptor_offset; VasEBoot_uint32_t total_compressed_size_including_seek_blocks_and_header2; VasEBoot_uint32_t value_0x32; VasEBoot_uint8_t unused[0xf0]; } VAS_EBOOT_PACKED; /* big-endian. */ struct VasEBoot_hfsplus_compress_header2 { VasEBoot_uint32_t total_compressed_size_including_seek_blocks; } VAS_EBOOT_PACKED; /* little-endian. */ struct VasEBoot_hfsplus_compress_header3 { VasEBoot_uint32_t num_chunks; } VAS_EBOOT_PACKED; /* little-endian. */ struct VasEBoot_hfsplus_compress_block_descriptor { VasEBoot_uint32_t offset; VasEBoot_uint32_t size; }; struct VasEBoot_hfsplus_compress_end_descriptor { VasEBoot_uint8_t always_the_same[50]; } VAS_EBOOT_PACKED; struct VasEBoot_hfsplus_attr_header { VasEBoot_uint8_t unused[3]; VasEBoot_uint8_t type; VasEBoot_uint32_t unknown[1]; VasEBoot_uint64_t size; } VAS_EBOOT_PACKED; struct VasEBoot_hfsplus_compress_attr { VasEBoot_uint32_t magic; VasEBoot_uint32_t type; VasEBoot_uint32_t uncompressed_inline_size; VasEBoot_uint32_t always_0; } VAS_EBOOT_PACKED; enum { HFSPLUS_COMPRESSION_INLINE = 3, HFSPLUS_COMPRESSION_RESOURCE = 4 }; static int VasEBoot_hfsplus_cmp_attrkey (struct VasEBoot_hfsplus_key *keya, struct VasEBoot_hfsplus_key_internal *keyb) { struct VasEBoot_hfsplus_attrkey *attrkey_a = &keya->attrkey; struct VasEBoot_hfsplus_attrkey_internal *attrkey_b = &keyb->attrkey; VasEBoot_uint32_t aparent = VasEBoot_be_to_cpu32 (attrkey_a->cnid); VasEBoot_size_t len; int diff; if (aparent > attrkey_b->cnid) return 1; if (aparent < attrkey_b->cnid) return -1; len = VasEBoot_be_to_cpu16 (attrkey_a->namelen); if (len > attrkey_b->namelen) len = attrkey_b->namelen; /* Since it's big-endian memcmp gives the same result as manually comparing uint16_t but may be faster. */ diff = VasEBoot_memcmp (attrkey_a->name, attrkey_b->name, len * sizeof (attrkey_a->name[0])); if (diff == 0) diff = VasEBoot_be_to_cpu16 (attrkey_a->namelen) - attrkey_b->namelen; return diff; } #define HFSPLUS_COMPRESS_BLOCK_SIZE 65536 static VasEBoot_ssize_t hfsplus_read_compressed_real (struct VasEBoot_hfsplus_file *node, VasEBoot_off_t pos, VasEBoot_size_t len, char *buf) { char *tmp_buf = 0; VasEBoot_size_t len0 = len; if (node->compressed == 1) { VasEBoot_memcpy (buf, node->cbuf + pos, len); if (VasEBoot_file_progress_hook && node->file) VasEBoot_file_progress_hook (0, 0, len, NULL, node->file); return len; } while (len) { VasEBoot_uint32_t block = pos / HFSPLUS_COMPRESS_BLOCK_SIZE; VasEBoot_size_t curlen = HFSPLUS_COMPRESS_BLOCK_SIZE - (pos % HFSPLUS_COMPRESS_BLOCK_SIZE); if (curlen > len) curlen = len; if (node->cbuf_block != block) { VasEBoot_uint32_t sz = VasEBoot_le_to_cpu32 (node->compress_index[block].size); VasEBoot_size_t ts; if (!tmp_buf) tmp_buf = VasEBoot_malloc (HFSPLUS_COMPRESS_BLOCK_SIZE); if (!tmp_buf) return -1; if (VasEBoot_hfsplus_read_file (node, 0, 0, VasEBoot_le_to_cpu32 (node->compress_index[block].start) + 0x104, sz, tmp_buf) != (VasEBoot_ssize_t) sz) { VasEBoot_free (tmp_buf); return -1; } ts = HFSPLUS_COMPRESS_BLOCK_SIZE; if (ts > node->size - (pos & ~(HFSPLUS_COMPRESS_BLOCK_SIZE))) ts = node->size - (pos & ~(HFSPLUS_COMPRESS_BLOCK_SIZE)); if (VasEBoot_zlib_decompress (tmp_buf, sz, 0, node->cbuf, ts) != (VasEBoot_ssize_t) ts) { if (!VasEBoot_errno) VasEBoot_error (VAS_EBOOT_ERR_BAD_COMPRESSED_DATA, "premature end of compressed"); VasEBoot_free (tmp_buf); return -1; } node->cbuf_block = block; } VasEBoot_memcpy (buf, node->cbuf + (pos % HFSPLUS_COMPRESS_BLOCK_SIZE), curlen); if (VasEBoot_file_progress_hook && node->file) VasEBoot_file_progress_hook (0, 0, curlen, NULL, node->file); buf += curlen; pos += curlen; len -= curlen; } VasEBoot_free (tmp_buf); return len0; } static VasEBoot_err_t hfsplus_open_compressed_real (struct VasEBoot_hfsplus_file *node) { VasEBoot_err_t err; struct VasEBoot_hfsplus_btnode *attr_node; VasEBoot_off_t attr_off; struct VasEBoot_hfsplus_key_internal key; struct VasEBoot_hfsplus_attr_header *attr_head; struct VasEBoot_hfsplus_compress_attr *cmp_head; #define c VasEBoot_cpu_to_be16_compile_time const VasEBoot_uint16_t compress_attr_name[] = { c('c'), c('o'), c('m'), c('.'), c('a'), c('p'), c('p'), c('l'), c('e'), c('.'), c('d'), c('e'), c('c'), c('m'), c('p'), c('f'), c('s') }; #undef c if (node->size) return 0; key.attrkey.cnid = node->fileid; key.attrkey.namelen = sizeof (compress_attr_name) / sizeof (compress_attr_name[0]); key.attrkey.name = compress_attr_name; err = VasEBoot_hfsplus_btree_search (&node->data->attr_tree, &key, VasEBoot_hfsplus_cmp_attrkey, &attr_node, &attr_off); if (err || !attr_node) { VasEBoot_errno = 0; return 0; } attr_head = (struct VasEBoot_hfsplus_attr_header *) ((char *) VasEBoot_hfsplus_btree_recptr (&node->data->attr_tree, attr_node, attr_off) + sizeof (struct VasEBoot_hfsplus_attrkey) + sizeof (compress_attr_name)); if (attr_head->type != 0x10 || !(attr_head->size & VasEBoot_cpu_to_be64_compile_time(~0xfULL))) { VasEBoot_free (attr_node); return 0; } cmp_head = (struct VasEBoot_hfsplus_compress_attr *) (attr_head + 1); if (cmp_head->magic != VasEBoot_cpu_to_be32_compile_time (0x66706d63)) { VasEBoot_free (attr_node); return 0; } node->size = VasEBoot_le_to_cpu32 (cmp_head->uncompressed_inline_size); if (cmp_head->type == VasEBoot_cpu_to_le32_compile_time (HFSPLUS_COMPRESSION_RESOURCE)) { VasEBoot_uint32_t index_size; node->compressed = 2; if (VasEBoot_hfsplus_read_file (node, 0, 0, 0x104, sizeof (index_size), (char *) &index_size) != 4) { node->compressed = 0; VasEBoot_free (attr_node); VasEBoot_errno = 0; return 0; } node->compress_index_size = VasEBoot_le_to_cpu32 (index_size); node->compress_index = VasEBoot_calloc (node->compress_index_size, sizeof (node->compress_index[0])); if (!node->compress_index) { node->compressed = 0; VasEBoot_free (attr_node); return VasEBoot_errno; } /* * The node->compress_index_size * sizeof (node->compress_index[0]) is safe here * due to relevant checks done in VasEBoot_calloc() above. */ if (VasEBoot_hfsplus_read_file (node, 0, 0, 0x104 + sizeof (index_size), node->compress_index_size * sizeof (node->compress_index[0]), (char *) node->compress_index) != (VasEBoot_ssize_t) (node->compress_index_size * sizeof (node->compress_index[0]))) { node->compressed = 0; VasEBoot_free (attr_node); VasEBoot_free (node->compress_index); VasEBoot_errno = 0; return 0; } node->cbuf_block = -1; node->cbuf = VasEBoot_malloc (HFSPLUS_COMPRESS_BLOCK_SIZE); VasEBoot_free (attr_node); if (!node->cbuf) { node->compressed = 0; VasEBoot_free (node->compress_index); return VasEBoot_errno; } return 0; } if (cmp_head->type != HFSPLUS_COMPRESSION_INLINE) { VasEBoot_free (attr_node); return 0; } node->cbuf = VasEBoot_malloc (node->size); if (!node->cbuf) return VasEBoot_errno; if (VasEBoot_zlib_decompress ((char *) (cmp_head + 1), VasEBoot_cpu_to_be64 (attr_head->size) - sizeof (*cmp_head), 0, node->cbuf, node->size) != (VasEBoot_ssize_t) node->size) { if (!VasEBoot_errno) VasEBoot_error (VAS_EBOOT_ERR_BAD_COMPRESSED_DATA, "premature end of compressed"); return VasEBoot_errno; } node->compressed = 1; return 0; } VAS_EBOOT_MOD_INIT(hfspluscomp) { VasEBoot_hfsplus_open_compressed = hfsplus_open_compressed_real; VasEBoot_hfsplus_read_compressed = hfsplus_read_compressed_real; } VAS_EBOOT_MOD_FINI(hfspluscomp) { VasEBoot_hfsplus_open_compressed = 0; VasEBoot_hfsplus_read_compressed = 0; }