/*! * \brief Find size of Linux kernel in boot image * * \pre The file position can be at any offset prior to calling this function. * * \post The file pointer position is undefined after this function returns. * Use File::seek() to return to a known position. * * \param[in] reader Reader to set error message * \param[in] file File handle * \param[in] kernel_offset Offset of kernel in boot image * \param[out] kernel_size_out Pointer to store kernel size * * \return * * Nothing if the kernel size is found * * FileError::UnexpectedEof if the kernel size cannot be found * * A specific error if any file operation fails */ oc::result<void> LokiFormatReader::find_linux_kernel_size(Reader &reader,File &file, uint32_t kernel_offset, uint32_t &kernel_size_out) { uint32_t kernel_size; // If the boot image was patched with an early version of loki, the // original kernel size is not stored in the loki header properly (or in the // shellcode). The size is stored in the kernel image's header though, so // we'll use that. // http://www.simtec.co.uk/products/SWLINUX/files/booting_article.html#d0e309 auto seek_ret = file.seek(kernel_offset + 0x2c, SEEK_SET); if (!seek_ret) { if (file.is_fatal()) { reader.set_fatal(); } return seek_ret.as_failure(); } auto ret = file_read_exact(file, &kernel_size, sizeof(kernel_size)); if (!ret) { if (file.is_fatal()) { reader.set_fatal(); } return ret.as_failure(); } kernel_size_out = mb_le32toh(kernel_size); return oc::success(); }
static oc::result<void> _mtk_header_update_size(Writer &writer, File &file, uint64_t offset, uint32_t size) { uint32_t le32_size = mb_htole32(size); if (offset > SIZE_MAX - offsetof(MtkHeader, size)) { writer.set_fatal(); return MtkError::MtkHeaderOffsetTooLarge; } auto seek_ret = file.seek( static_cast<int64_t>(offset + offsetof(MtkHeader, size)), SEEK_SET); if (!seek_ret) { if (file.is_fatal()) { writer.set_fatal(); } return seek_ret.as_failure(); } auto ret = file_write_exact(file, &le32_size, sizeof(le32_size)); if (!ret) { if (file.is_fatal()) { writer.set_fatal(); } return ret.as_failure(); } return oc::success(); }
/*! * \brief Find and read Loki boot image header * * \note The integral fields in the header will be converted to the host's byte * order. * * \pre The file position can be at any offset prior to calling this function. * * \post The file pointer position is undefined after this function returns. * Use File::seek() to return to a known position. * * \param[in] reader Reader * \param[in] file File handle * \param[out] header_out Pointer to store header * \param[out] offset_out Pointer to store header offset * * \return * * Nothing if the header is found * * A LokiError if the header is not found * * A specified error code if any file operation fails */ oc::result<void> LokiFormatReader::find_loki_header(Reader &reader, File &file, LokiHeader &header_out, uint64_t &offset_out) { LokiHeader header; auto seek_ret = file.seek(LOKI_MAGIC_OFFSET, SEEK_SET); if (!seek_ret) { if (file.is_fatal()) { reader.set_fatal(); } return seek_ret.as_failure(); } auto ret = file_read_exact(file, &header, sizeof(header)); if (!ret) { if (ret.error() == FileError::UnexpectedEof) { return LokiError::LokiHeaderTooSmall; } else { if (file.is_fatal()) { reader.set_fatal(); } return ret.as_failure(); } } if (memcmp(header.magic, LOKI_MAGIC, LOKI_MAGIC_SIZE) != 0) { return LokiError::InvalidLokiMagic; } loki_fix_header_byte_order(header); header_out = header; offset_out = LOKI_MAGIC_OFFSET; return oc::success(); }
oc::result<void> SegmentReader::move_to_entry(File &file, Entry &entry, std::vector<SegmentReaderEntry>::iterator srentry, Reader &reader) { if (srentry->offset > UINT64_MAX - srentry->size) { return SegmentError::EntryWouldOverflowOffset; } uint64_t read_start_offset = srentry->offset; uint64_t read_end_offset = read_start_offset + srentry->size; uint64_t read_cur_offset = read_start_offset; if (m_read_cur_offset != srentry->offset) { auto ret = file.seek(static_cast<int64_t>(read_start_offset), SEEK_SET); if (!ret) { if (file.is_fatal()) { reader.set_fatal(); } return ret.as_failure(); } } entry.set_type(srentry->type); entry.set_size(srentry->size); m_state = SegmentReaderState::Entries; m_entry = srentry; m_read_start_offset = read_start_offset; m_read_end_offset = read_end_offset; m_read_cur_offset = read_cur_offset; return oc::success(); }
oc::result<size_t> SegmentReader::read_data(File &file, void *buf, size_t buf_size, Reader &reader) { auto to_copy = static_cast<size_t>(std::min<uint64_t>( buf_size, m_read_end_offset - m_read_cur_offset)); if (m_read_cur_offset > SIZE_MAX - to_copy) { //DEBUG("Current offset %" PRIu64 " with read size %" MB_PRIzu // " would overflow integer", m_read_cur_offset, to_copy); return SegmentError::ReadWouldOverflowInteger; } // We allow truncation for certain things, so we can't use file_read_exact() auto n = file_read_retry(file, buf, to_copy); if (!n) { if (file.is_fatal()) { reader.set_fatal(); } return n.as_failure(); } m_read_cur_offset += n.value(); // Fail if we reach EOF early if (n.value() < to_copy && m_read_cur_offset != m_read_end_offset && !m_entry->can_truncate) { //DEBUG("Entry is truncated (expected %" PRIu64 " more bytes)", // m_read_end_offset - m_read_cur_offset); reader.set_fatal(); return FileError::UnexpectedEof; } return n.value(); }
oc::result<void> selinux_lset_context_recursive(const std::string &path, const std::string &context) { RecursiveSetContext rsc(path, context, false); if (!rsc.run()) { auto ret = rsc.result(); if (ret) { return ec_from_errno(); } else { return ret.as_failure(); } } return rsc.result(); }
oc::result<void> FileUtils::open_file(StandardFile &file, const std::string &path, FileOpenMode mode) { #ifdef _WIN32 auto w_filename = utf8_to_wcs(path); if (!w_filename) { LOGE("%s: Failed to convert from UTF8 to WCS: %s", path.c_str(), w_filename.error().message().c_str()); return w_filename.as_failure(); } return file.open(w_filename.value(), mode); #else return file.open(path, mode); #endif }
static oc::result<void> _mtk_compute_sha1(Writer &writer, SegmentWriter &seg, File &file, unsigned char digest[SHA_DIGEST_LENGTH]) { SHA_CTX sha_ctx; char buf[10240]; uint32_t kernel_mtkhdr_size = 0; uint32_t ramdisk_mtkhdr_size = 0; if (!SHA1_Init(&sha_ctx)) { return android::AndroidError::Sha1InitError; } for (auto const &entry : seg.entries()) { uint64_t remain = *entry.size; auto seek_ret = file.seek(static_cast<int64_t>(entry.offset), SEEK_SET); if (!seek_ret) { if (file.is_fatal()) { writer.set_fatal(); } return seek_ret.as_failure(); } // Update checksum with data while (remain > 0) { auto to_read = std::min<uint64_t>(remain, sizeof(buf)); auto ret = file_read_exact(file, buf, static_cast<size_t>(to_read)); if (!ret) { if (writer.is_fatal()) { writer.set_fatal(); } return ret.as_failure(); } if (!SHA1_Update(&sha_ctx, buf, static_cast<size_t>(to_read))) { return android::AndroidError::Sha1UpdateError; } remain -= to_read; } uint32_t le32_size; // Update checksum with size switch (entry.type) { case ENTRY_TYPE_MTK_KERNEL_HEADER: kernel_mtkhdr_size = *entry.size; continue; case ENTRY_TYPE_MTK_RAMDISK_HEADER: ramdisk_mtkhdr_size = *entry.size; continue; case ENTRY_TYPE_KERNEL: le32_size = mb_htole32(*entry.size + kernel_mtkhdr_size); break; case ENTRY_TYPE_RAMDISK: le32_size = mb_htole32(*entry.size + ramdisk_mtkhdr_size); break; case ENTRY_TYPE_SECONDBOOT: le32_size = mb_htole32(*entry.size); break; case ENTRY_TYPE_DEVICE_TREE: if (*entry.size == 0) { continue; } le32_size = mb_htole32(*entry.size); break; default: continue; } if (!SHA1_Update(&sha_ctx, &le32_size, sizeof(le32_size))) { return android::AndroidError::Sha1UpdateError; } } if (!SHA1_Final(digest, &sha_ctx)) { return android::AndroidError::Sha1UpdateError; } return oc::success(); }
oc::result<void> MtkFormatWriter::write_header(File &file, const Header &header) { // Construct header m_hdr = {}; memcpy(m_hdr.magic, android::BOOT_MAGIC, android::BOOT_MAGIC_SIZE); if (auto address = header.kernel_address()) { m_hdr.kernel_addr = *address; } if (auto address = header.ramdisk_address()) { m_hdr.ramdisk_addr = *address; } if (auto address = header.secondboot_address()) { m_hdr.second_addr = *address; } if (auto address = header.kernel_tags_address()) { m_hdr.tags_addr = *address; } if (auto page_size = header.page_size()) { switch (*page_size) { case 2048: case 4096: case 8192: case 16384: case 32768: case 65536: case 131072: m_hdr.page_size = *page_size; break; default: //DEBUG("Invalid page size: %" PRIu32, *page_size); return android::AndroidError::InvalidPageSize; } } else { return android::AndroidError::MissingPageSize; } if (auto board_name = header.board_name()) { if (board_name->size() >= sizeof(m_hdr.name)) { return android::AndroidError::BoardNameTooLong; } strncpy(reinterpret_cast<char *>(m_hdr.name), board_name->c_str(), sizeof(m_hdr.name) - 1); m_hdr.name[sizeof(m_hdr.name) - 1] = '\0'; } if (auto cmdline = header.kernel_cmdline()) { if (cmdline->size() >= sizeof(m_hdr.cmdline)) { return android::AndroidError::KernelCmdlineTooLong; } strncpy(reinterpret_cast<char *>(m_hdr.cmdline), cmdline->c_str(), sizeof(m_hdr.cmdline) - 1); m_hdr.cmdline[sizeof(m_hdr.cmdline) - 1] = '\0'; } // TODO: UNUSED // TODO: ID std::vector<SegmentWriterEntry> entries; entries.push_back({ ENTRY_TYPE_MTK_KERNEL_HEADER, 0, {}, 0 }); entries.push_back({ ENTRY_TYPE_KERNEL, 0, {}, m_hdr.page_size }); entries.push_back({ ENTRY_TYPE_MTK_RAMDISK_HEADER, 0, {}, 0 }); entries.push_back({ ENTRY_TYPE_RAMDISK, 0, {}, m_hdr.page_size }); entries.push_back({ ENTRY_TYPE_SECONDBOOT, 0, {}, m_hdr.page_size }); entries.push_back({ ENTRY_TYPE_DEVICE_TREE, 0, {}, m_hdr.page_size }); OUTCOME_TRYV(m_seg->set_entries(std::move(entries))); // Start writing after first page auto seek_ret = file.seek(m_hdr.page_size, SEEK_SET); if (!seek_ret) { if (file.is_fatal()) { m_writer.set_fatal(); } return seek_ret.as_failure(); } return oc::success(); }
oc::result<void> MtkFormatWriter::close(File &file) { auto reset_state = finally([&] { m_hdr = {}; m_seg = {}; }); if (m_writer.is_open()) { auto swentry = m_seg->entry(); // If successful, finish up the boot image if (swentry == m_seg->entries().end()) { auto file_size = file.seek(0, SEEK_CUR); if (!file_size) { if (file.is_fatal()) { m_writer.set_fatal(); } return file_size.as_failure(); } // Truncate to set size auto truncate_ret = file.truncate(file_size.value()); if (!truncate_ret) { if (file.is_fatal()) { m_writer.set_fatal(); } return truncate_ret.as_failure(); } // Update MTK header sizes for (auto const &entry : m_seg->entries()) { if (entry.type == ENTRY_TYPE_MTK_KERNEL_HEADER) { OUTCOME_TRYV(_mtk_header_update_size( m_writer, file, entry.offset, static_cast<uint32_t>( m_hdr.kernel_size - sizeof(MtkHeader)))); } else if (entry.type == ENTRY_TYPE_MTK_RAMDISK_HEADER) { OUTCOME_TRYV(_mtk_header_update_size( m_writer, file, entry.offset, static_cast<uint32_t>( m_hdr.ramdisk_size - sizeof(MtkHeader)))); } } // We need to take the performance hit and compute the SHA1 here. // We can't fill in the sizes in the MTK headers when we're writing // them. Thus, if we calculated the SHA1sum during write, it would // be incorrect. OUTCOME_TRYV(_mtk_compute_sha1( m_writer, *m_seg, file, reinterpret_cast<unsigned char *>(m_hdr.id))); // Convert fields back to little-endian android_fix_header_byte_order(m_hdr); // Seek back to beginning to write header auto seek_ret = file.seek(0, SEEK_SET); if (!seek_ret) { if (file.is_fatal()) { m_writer.set_fatal(); } return seek_ret.as_failure(); } // Write header auto ret = file_write_exact(file, &m_hdr, sizeof(m_hdr)); if (!ret) { if (file.is_fatal()) { m_writer.set_fatal(); } return ret.as_failure(); } } } return oc::success(); }
oc::result<void> SonyElfFormatWriter::write_header(File &file, const Header &header) { m_cmdline.clear(); m_hdr = {}; m_hdr_kernel = {}; m_hdr_ramdisk = {}; m_hdr_cmdline = {}; m_hdr_ipl = {}; m_hdr_rpm = {}; m_hdr_appsbl = {}; // Construct ELF header memcpy(&m_hdr.e_ident, SONY_E_IDENT, SONY_EI_NIDENT); m_hdr.e_type = 2; m_hdr.e_machine = 40; m_hdr.e_version = 1; m_hdr.e_phoff = 52; m_hdr.e_shoff = 0; m_hdr.e_flags = 0; m_hdr.e_ehsize = sizeof(Sony_Elf32_Ehdr); m_hdr.e_phentsize = sizeof(Sony_Elf32_Phdr); m_hdr.e_shentsize = 0; m_hdr.e_shnum = 0; m_hdr.e_shstrndx = 0; if (auto entrypoint_address = header.entrypoint_address()) { m_hdr.e_entry = *entrypoint_address; } else if (auto kernel_address = header.kernel_address()) { m_hdr.e_entry = *kernel_address; } // Construct kernel program header m_hdr_kernel.p_type = SONY_E_TYPE_KERNEL; m_hdr_kernel.p_flags = SONY_E_FLAGS_KERNEL; m_hdr_kernel.p_align = 0; if (auto address = header.kernel_address()) { m_hdr_kernel.p_vaddr = *address; m_hdr_kernel.p_paddr = *address; } // Construct ramdisk program header m_hdr_ramdisk.p_type = SONY_E_TYPE_RAMDISK; m_hdr_ramdisk.p_flags = SONY_E_FLAGS_RAMDISK; m_hdr_ramdisk.p_align = 0; if (auto address = header.ramdisk_address()) { m_hdr_ramdisk.p_vaddr = *address; m_hdr_ramdisk.p_paddr = *address; } // Construct cmdline program header m_hdr_cmdline.p_type = SONY_E_TYPE_CMDLINE; m_hdr_cmdline.p_vaddr = 0; m_hdr_cmdline.p_paddr = 0; m_hdr_cmdline.p_flags = SONY_E_FLAGS_CMDLINE; m_hdr_cmdline.p_align = 0; if (auto cmdline = header.kernel_cmdline()) { m_cmdline = *cmdline; } // Construct IPL program header m_hdr_ipl.p_type = SONY_E_TYPE_IPL; m_hdr_ipl.p_flags = SONY_E_FLAGS_IPL; m_hdr_ipl.p_align = 0; if (auto address = header.sony_ipl_address()) { m_hdr_ipl.p_vaddr = *address; m_hdr_ipl.p_paddr = *address; } // Construct RPM program header m_hdr_rpm.p_type = SONY_E_TYPE_RPM; m_hdr_rpm.p_flags = SONY_E_FLAGS_RPM; m_hdr_rpm.p_align = 0; if (auto address = header.sony_rpm_address()) { m_hdr_rpm.p_vaddr = *address; m_hdr_rpm.p_paddr = *address; } // Construct APPSBL program header m_hdr_appsbl.p_type = SONY_E_TYPE_APPSBL; m_hdr_appsbl.p_flags = SONY_E_FLAGS_APPSBL; m_hdr_appsbl.p_align = 0; if (auto address = header.sony_appsbl_address()) { m_hdr_appsbl.p_vaddr = *address; m_hdr_appsbl.p_paddr = *address; } std::vector<SegmentWriterEntry> entries; entries.push_back({ ENTRY_TYPE_KERNEL, 0, {}, 0 }); entries.push_back({ ENTRY_TYPE_RAMDISK, 0, {}, 0 }); entries.push_back({ SONY_ELF_ENTRY_CMDLINE, 0, {}, 0 }); entries.push_back({ ENTRY_TYPE_SONY_IPL, 0, {}, 0 }); entries.push_back({ ENTRY_TYPE_SONY_RPM, 0, {}, 0 }); entries.push_back({ ENTRY_TYPE_SONY_APPSBL, 0, {}, 0 }); OUTCOME_TRYV(m_seg->set_entries(std::move(entries))); // Start writing at offset 4096 auto seek_ret = file.seek(4096, SEEK_SET); if (!seek_ret) { if (file.is_fatal()) { m_writer.set_fatal(); } return seek_ret.as_failure(); } return oc::success(); }
/*! * \brief Find ramdisk size in old-style Loki image * * \pre The file position can be at any offset prior to calling this function. * * \post The file pointer position is undefined after this function returns. * Use File::seek() to return to a known position. * * \param[in] reader Reader to set error message * \param[in] file File handle * \param[in] hdr Android header * \param[in] ramdisk_offset Offset of ramdisk in image * \param[out] ramdisk_size_out Pointer to store ramdisk size * * \return * * Nothing if the ramdisk size is found * * A LokiError if the ramdisk size is not found * * A specific error if any file operation fails */ oc::result<void> LokiFormatReader::find_ramdisk_size_old(Reader &reader, File &file, const android::AndroidHeader &hdr, uint32_t ramdisk_offset, uint32_t &ramdisk_size_out) { int32_t aboot_size; // If the boot image was patched with an old version of loki, the ramdisk // size is not stored properly. We'll need to guess the size of the archive. // The ramdisk is supposed to be from the gzip header to EOF, but loki needs // to store a copy of aboot, so it is put in the last 0x200 bytes of the // file. if (is_lg_ramdisk_address(hdr.ramdisk_addr)) { aboot_size = static_cast<int32_t>(hdr.page_size); } else { aboot_size = 0x200; } auto aboot_offset = file.seek(-aboot_size, SEEK_END); if (!aboot_offset) { if (file.is_fatal()) { reader.set_fatal(); } return aboot_offset.as_failure(); } if (ramdisk_offset > aboot_offset.value()) { return LokiError::RamdiskOffsetGreaterThanAbootOffset; } // Ignore zero padding as we might strip away too much #if 1 ramdisk_size_out = static_cast<uint32_t>( aboot_offset.value() - ramdisk_offset); return oc::success(); #else char buf[1024]; // Search backwards to find non-zero byte uint64_t cur_offset = aboot_offset.value(); while (cur_offset > ramdisk_offset) { size_t to_read = std::min<uint64_t>( sizeof(buf), cur_offset - ramdisk_offset); cur_offset -= to_read; auto seek_ret = file.seek(cur_offset, SEEK_SET); if (!seek_ret) { if (file.is_fatal()) { reader.set_fatal(); } return seek_ret.as_failure(); } auto ret = file_read_exact(file, buf, to_read); if (!ret) { if (file.is_fatal()) { reader.set_fatal(); } return ret.as_failure(); } for (size_t i = to_read; i-- > 0; ) { if (buf[i] != '\0') { ramdisk_size_out = cur_offset - ramdisk_offset + i; return oc::success(); } } } return LokiError::FailedToDetermineRamdiskSize; #endif }
/*! * \brief Find gzip ramdisk offset in old-style Loki image * * This function will search for gzip headers (`0x1f8b08`) with a flags byte of * `0x00` or `0x08`. It will find the first occurrence of either magic string. * If both are found, the one with the flags byte set to `0x08` takes precedence * as it indiciates that the original filename field is set. This is usually the * case for ramdisks packed via the `gzip` command line tool. * * \pre The file position can be at any offset prior to calling this function. * * \post The file pointer position is undefined after this function returns. * Use File::seek() to return to a known position. * * \param[in] reader Reader to set error message * \param[in] file File handle * \param[in] start_offset Starting offset for search * \param[out] gzip_offset_out Pointer to store gzip ramdisk offset * * \return * * Nothing if a gzip offset is found * * A LokiError if no gzip offsets are found * * A specific error if any file operation fails */ oc::result<void> LokiFormatReader::find_gzip_offset_old(Reader &reader, File &file, uint32_t start_offset, uint64_t &gzip_offset_out) { struct SearchResult { std::optional<uint64_t> flag0_offset; std::optional<uint64_t> flag8_offset; }; // gzip header: // byte 0-1 : magic bytes 0x1f, 0x8b // byte 2 : compression (0x08 = deflate) // byte 3 : flags // byte 4-7 : modification timestamp // byte 8 : compression flags // byte 9 : operating system static const unsigned char gzip_deflate_magic[] = { 0x1f, 0x8b, 0x08 }; SearchResult result = {}; // Find first result with flags == 0x00 and flags == 0x08 auto result_cb = [&](File &file_, uint64_t offset) -> oc::result<FileSearchAction> { unsigned char flags; // Stop early if possible if (result.flag0_offset && result.flag8_offset) { return FileSearchAction::Stop; } // Save original position OUTCOME_TRY(orig_offset, file_.seek(0, SEEK_CUR)); // Seek to flags byte OUTCOME_TRYV(file_.seek(static_cast<int64_t>(offset + 3), SEEK_SET)); // Read next bytes for flags auto ret = file_read_exact(file_, &flags, sizeof(flags)); if (!ret) { if (ret.error() == FileError::UnexpectedEof) { return FileSearchAction::Stop; } else { return ret.as_failure(); } } if (!result.flag0_offset && flags == 0x00) { result.flag0_offset = offset; } else if (!result.flag8_offset && flags == 0x08) { result.flag8_offset = offset; } // Restore original position as per contract OUTCOME_TRYV(file_.seek(static_cast<int64_t>(orig_offset), SEEK_SET)); return FileSearchAction::Continue; }; auto ret = file_search(file, start_offset, {}, 0, gzip_deflate_magic, sizeof(gzip_deflate_magic), {}, result_cb); if (!ret) { if (file.is_fatal()) { reader.set_fatal(); } return ret.as_failure(); } // Prefer gzip header with original filename flag since most loki'd boot // images will have been compressed manually with the gzip tool if (result.flag8_offset) { gzip_offset_out = *result.flag8_offset; } else if (result.flag0_offset) { gzip_offset_out = *result.flag0_offset; } else { return LokiError::NoRamdiskGzipHeaderFound; } return oc::success(); }
/*! * \brief Find and read Loki ramdisk address * * \pre The file position can be at any offset prior to calling this function. * * \post The file pointer position is undefined after this function returns. * Use File::seek() to return to a known position. * * \param[in] reader Reader to set error message * \param[in] file File handle * \param[in] hdr Android header * \param[in] loki_hdr Loki header * \param[out] ramdisk_addr_out Pointer to store ramdisk address * * \return * * Nothing if the ramdisk address is found * * A LokiError if the ramdisk address is not found * * A specific error code if any file operation fails */ oc::result<void> LokiFormatReader::find_ramdisk_address(Reader &reader, File &file, const android::AndroidHeader &hdr, const LokiHeader &loki_hdr, uint32_t &ramdisk_addr_out) { // If the boot image was patched with a newer version of loki, find the // ramdisk offset in the shell code uint32_t ramdisk_addr = 0; if (loki_hdr.ramdisk_addr != 0) { uint64_t offset = 0; auto result_cb = [&](File &file_, uint64_t offset_) -> oc::result<FileSearchAction> { (void) file_; offset = offset_; return FileSearchAction::Continue; }; auto ret = file_search(file, {}, {}, 0, LOKI_SHELLCODE, LOKI_SHELLCODE_SIZE - 9, 1, result_cb); if (!ret) { if (file.is_fatal()) { reader.set_fatal(); } return ret.as_failure(); } if (offset == 0) { return LokiError::ShellcodeNotFound; } offset += LOKI_SHELLCODE_SIZE - 5; auto seek_ret = file.seek(static_cast<int64_t>(offset), SEEK_SET); if (!seek_ret) { if (file.is_fatal()) { reader.set_fatal(); } return seek_ret.as_failure(); } auto read_ret = file_read_exact(file, &ramdisk_addr, sizeof(ramdisk_addr)); if (!read_ret) { if (file.is_fatal()) { reader.set_fatal(); } return read_ret.as_failure(); } ramdisk_addr = mb_le32toh(ramdisk_addr); } else { // Otherwise, use the default for jflte (- 0x00008000 + 0x02000000) if (hdr.kernel_addr > UINT32_MAX - 0x01ff8000) { //DEBUG("Invalid kernel address: %" PRIu32, hdr.kernel_addr); return LokiError::InvalidKernelAddress; } ramdisk_addr = hdr.kernel_addr + 0x01ff8000; } ramdisk_addr_out = ramdisk_addr; return oc::success(); }