diff --git a/.gitignore b/.gitignore index 2ff9f77b3..eed9b314a 100644 --- a/.gitignore +++ b/.gitignore @@ -13,7 +13,7 @@ native/out # Android Studio / Gradle *.iml .gradle +.idea /local.properties -/.idea /build /captures diff --git a/native/src/base/cxx_extern.rs b/native/src/base/cxx_extern.rs index 6ddae9124..7edaf8913 100644 --- a/native/src/base/cxx_extern.rs +++ b/native/src/base/cxx_extern.rs @@ -7,6 +7,7 @@ use cfg_if::cfg_if; use cxx::private::c_char; use libc::mode_t; +use crate::files::map_file_at; pub(crate) use crate::xwrap::*; use crate::{ clone_attr, cstr, fclone_attr, fd_path, map_fd, map_file, slice_from_ptr, CxxResultExt, @@ -61,6 +62,14 @@ pub(crate) fn map_file_for_cxx(path: &Utf8CStr, rw: bool) -> &'static mut [u8] { map_file(path, rw).log_cxx().unwrap_or(&mut []) } +pub(crate) fn map_file_at_for_cxx(fd: RawFd, path: &Utf8CStr, rw: bool) -> &'static mut [u8] { + unsafe { + map_file_at(BorrowedFd::borrow_raw(fd), path, rw) + .log_cxx() + .unwrap_or(&mut []) + } +} + pub(crate) fn map_fd_for_cxx(fd: RawFd, sz: usize, rw: bool) -> &'static mut [u8] { unsafe { map_fd(BorrowedFd::borrow_raw(fd), sz, rw) diff --git a/native/src/base/files.cpp b/native/src/base/files.cpp index 8b0c31231..eacd780e7 100644 --- a/native/src/base/files.cpp +++ b/native/src/base/files.cpp @@ -117,6 +117,14 @@ mmap_data::mmap_data(const char *name, bool rw) { } } +mmap_data::mmap_data(int dirfd, const char *name, bool rw) { + auto slice = rust::map_file_at(dirfd, name, rw); + if (!slice.empty()) { + _buf = slice.data(); + _sz = slice.size(); + } +} + mmap_data::mmap_data(int fd, size_t sz, bool rw) { auto slice = rust::map_fd(fd, sz, rw); if (!slice.empty()) { diff --git a/native/src/base/files.hpp b/native/src/base/files.hpp index 03fd9bada..073e854e5 100644 --- a/native/src/base/files.hpp +++ b/native/src/base/files.hpp @@ -26,6 +26,7 @@ struct mmap_data : public byte_data { ALLOW_MOVE_ONLY(mmap_data) explicit mmap_data(const char *name, bool rw = false); + mmap_data(int dirfd, const char *name, bool rw = false); mmap_data(int fd, size_t sz, bool rw = false); ~mmap_data(); }; diff --git a/native/src/base/files.rs b/native/src/base/files.rs index 2ff7f7520..335bf50b7 100644 --- a/native/src/base/files.rs +++ b/native/src/base/files.rs @@ -856,6 +856,14 @@ impl MappedFile { Ok(MappedFile(map_file(path, true)?)) } + pub fn openat(dir: &T, path: &Utf8CStr) -> io::Result { + Ok(MappedFile(map_file_at(dir.as_fd(), path, false)?)) + } + + pub fn openat_rw(dir: &T, path: &Utf8CStr) -> io::Result { + Ok(MappedFile(map_file_at(dir.as_fd(), path, true)?)) + } + pub fn create(fd: BorrowedFd, sz: usize, rw: bool) -> io::Result { Ok(MappedFile(map_fd(fd, sz, rw)?)) } @@ -888,6 +896,14 @@ extern "C" { // We mark the returned slice static because it is valid until explicitly unmapped pub(crate) fn map_file(path: &Utf8CStr, rw: bool) -> io::Result<&'static mut [u8]> { + unsafe { map_file_at(BorrowedFd::borrow_raw(libc::AT_FDCWD), path, rw) } +} + +pub(crate) fn map_file_at( + dirfd: BorrowedFd, + path: &Utf8CStr, + rw: bool, +) -> io::Result<&'static mut [u8]> { #[cfg(target_pointer_width = "64")] const BLKGETSIZE64: u32 = 0x80081272; @@ -895,18 +911,22 @@ pub(crate) fn map_file(path: &Utf8CStr, rw: bool) -> io::Result<&'static mut [u8 const BLKGETSIZE64: u32 = 0x80041272; let flag = if rw { O_RDWR } else { O_RDONLY }; - let file = FsPath::from(path).open(flag | O_CLOEXEC)?; + let fd = unsafe { + OwnedFd::from_raw_fd( + libc::openat(dirfd.as_raw_fd(), path.as_ptr(), flag | O_CLOEXEC).check_os_err()?, + ) + }; - let attr = fd_get_attr(file.as_raw_fd())?; + let attr = fd_get_attr(fd.as_raw_fd())?; let sz = if attr.is_block_device() { let mut sz = 0_u64; - unsafe { ioctl(file.as_raw_fd(), BLKGETSIZE64, &mut sz) }.as_os_err()?; + unsafe { ioctl(fd.as_raw_fd(), BLKGETSIZE64, &mut sz) }.as_os_err()?; sz } else { attr.st.st_size as u64 }; - map_fd(file.as_fd(), sz as usize, rw) + map_fd(fd.as_fd(), sz as usize, rw) } pub(crate) fn map_fd(fd: BorrowedFd, sz: usize, rw: bool) -> io::Result<&'static mut [u8]> { diff --git a/native/src/base/lib.rs b/native/src/base/lib.rs index 52c15e696..e418a0042 100644 --- a/native/src/base/lib.rs +++ b/native/src/base/lib.rs @@ -61,6 +61,8 @@ pub mod ffi { fn fd_path_for_cxx(fd: i32, buf: &mut [u8]) -> isize; #[cxx_name = "map_file"] fn map_file_for_cxx(path: Utf8CStrRef, rw: bool) -> &'static mut [u8]; + #[cxx_name = "map_file_at"] + fn map_file_at_for_cxx(fd: i32, path: Utf8CStrRef, rw: bool) -> &'static mut [u8]; #[cxx_name = "map_fd"] fn map_fd_for_cxx(fd: i32, sz: usize, rw: bool) -> &'static mut [u8]; } diff --git a/native/src/boot/bootimg.cpp b/native/src/boot/bootimg.cpp index bb6076768..29d14a3cb 100644 --- a/native/src/boot/bootimg.cpp +++ b/native/src/boot/bootimg.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include @@ -61,6 +62,8 @@ void dyn_img_hdr::print() const { fprintf(stderr, "%-*s [%u]\n", PADDING, "RECOV_DTBO_SZ", recovery_dtbo_size()); if (ver == 2 || is_vendor()) fprintf(stderr, "%-*s [%u]\n", PADDING, "DTB_SZ", dtb_size()); + if (ver == 4 && is_vendor()) + fprintf(stderr, "%-*s [%u]\n", PADDING, "BOOTCONFIG_SZ", bootconfig_size()); if (uint32_t os_ver = os_version()) { int a,b,c,y,m = 0; @@ -273,8 +276,7 @@ static format_t check_fmt_lg(const uint8_t *buf, unsigned sz) { #define CMD_MATCH(s) BUFFER_MATCH(h->cmdline, s) -const pair - boot_img::create_hdr(const uint8_t *addr, format_t type) { +pair boot_img::create_hdr(const uint8_t *addr, format_t type) { if (type == AOSP_VENDOR) { fprintf(stderr, "VENDOR_BOOT_HDR\n"); auto h = reinterpret_cast(addr); @@ -346,10 +348,23 @@ const pair addr += ACCLAIM_PRE_HEADER_SZ; } - // addr could be adjusted return make_pair(addr, make_hdr(addr)); } +static const char *vendor_ramdisk_type(int type) { + switch (type) { + case VENDOR_RAMDISK_TYPE_PLATFORM: + return "platform"; + case VENDOR_RAMDISK_TYPE_RECOVERY: + return "recovery"; + case VENDOR_RAMDISK_TYPE_DLKM: + return "dlkm"; + case VENDOR_RAMDISK_TYPE_NONE: + default: + return "none"; + } +} + #define assert_off() \ if ((base_addr + off) > (map.buf() + map.sz())) { \ fprintf(stderr, "Corrupted boot image!\n"); \ @@ -362,14 +377,6 @@ off += hdr->name##_size(); \ off = align_to(off, hdr->page_size()); \ assert_off(); -#define get_ignore(name) \ -if (hdr->name##_size()) { \ - auto blk_sz = align_to(hdr->name##_size(), hdr->page_size()); \ - off += blk_sz; \ -} \ -assert_off(); - - bool boot_img::parse_image(const uint8_t *p, format_t type) { auto [base_addr, hdr] = create_hdr(p, type); if (hdr == nullptr) { @@ -395,15 +402,12 @@ bool boot_img::parse_image(const uint8_t *p, format_t type) { get_block(extra); get_block(recovery_dtbo); get_block(dtb); - - auto ignore_addr = base_addr + off; - get_ignore(signature) - get_ignore(vendor_ramdisk_table) - get_ignore(bootconfig) + get_block(signature); + get_block(vendor_ramdisk_table); + get_block(bootconfig); payload = byte_view(base_addr, off); auto tail_addr = base_addr + off; - ignore = byte_view(ignore_addr, tail_addr - ignore_addr); tail = byte_view(tail_addr, map.buf() + map.sz() - tail_addr); if (auto size = hdr->kernel_size()) { @@ -458,24 +462,40 @@ bool boot_img::parse_image(const uint8_t *p, format_t type) { fprintf(stderr, "%-*s [%s]\n", PADDING, "KERNEL_FMT", fmt2name[k_fmt]); } if (auto size = hdr->ramdisk_size()) { - if (hdr->is_vendor() && hdr->header_version() >= 4) { + if (vendor_ramdisk_table != nullptr) { // v4 vendor boot contains multiple ramdisks - // Do not try to mess with it for now - r_fmt = UNKNOWN; + using table_entry = const vendor_ramdisk_table_entry_v4; + if (hdr->vendor_ramdisk_table_entry_size() != sizeof(table_entry)) { + fprintf(stderr, + "! Invalid vendor image: vendor_ramdisk_table_entry_size != %zu\n", + sizeof(table_entry)); + exit(1); + } + + span table( + reinterpret_cast(vendor_ramdisk_table), + hdr->vendor_ramdisk_table_entry_num()); + for (auto &it : table) { + format_t fmt = check_fmt_lg(ramdisk + it.ramdisk_offset, it.ramdisk_size); + fprintf(stderr, + "%-*s name=[%s] type=[%s] size=[%u] fmt=[%s]\n", PADDING, "VND_RAMDISK", + it.ramdisk_name, vendor_ramdisk_type(it.ramdisk_type), + it.ramdisk_size, fmt2name[fmt]); + } } else { r_fmt = check_fmt_lg(ramdisk, size); + if (r_fmt == MTK) { + fprintf(stderr, "MTK_RAMDISK_HDR\n"); + flags[MTK_RAMDISK] = true; + r_hdr = reinterpret_cast(ramdisk); + fprintf(stderr, "%-*s [%u]\n", PADDING, "SIZE", r_hdr->size); + fprintf(stderr, "%-*s [%s]\n", PADDING, "NAME", r_hdr->name); + ramdisk += sizeof(mtk_hdr); + hdr->ramdisk_size() -= sizeof(mtk_hdr); + r_fmt = check_fmt_lg(ramdisk, hdr->ramdisk_size()); + } + fprintf(stderr, "%-*s [%s]\n", PADDING, "RAMDISK_FMT", fmt2name[r_fmt]); } - if (r_fmt == MTK) { - fprintf(stderr, "MTK_RAMDISK_HDR\n"); - flags[MTK_RAMDISK] = true; - r_hdr = reinterpret_cast(ramdisk); - fprintf(stderr, "%-*s [%u]\n", PADDING, "SIZE", r_hdr->size); - fprintf(stderr, "%-*s [%s]\n", PADDING, "NAME", r_hdr->name); - ramdisk += sizeof(mtk_hdr); - hdr->ramdisk_size() -= sizeof(mtk_hdr); - r_fmt = check_fmt_lg(ramdisk, hdr->ramdisk_size()); - } - fprintf(stderr, "%-*s [%s]\n", PADDING, "RAMDISK_FMT", fmt2name[r_fmt]); } if (auto size = hdr->extra_size()) { e_fmt = check_fmt_lg(extra, size); @@ -561,7 +581,30 @@ int unpack(const char *image, bool skip_decomp, bool hdr) { dump(boot.kernel_dtb.buf(), boot.kernel_dtb.sz(), KER_DTB_FILE); // Dump ramdisk - if (!skip_decomp && COMPRESSED(boot.r_fmt)) { + if (boot.vendor_ramdisk_table != nullptr) { + using table_entry = const vendor_ramdisk_table_entry_v4; + span table( + reinterpret_cast(boot.vendor_ramdisk_table), + boot.hdr->vendor_ramdisk_table_entry_num()); + + xmkdir(VND_RAMDISK_DIR, 0755); + owned_fd dirfd = xopen(VND_RAMDISK_DIR, O_RDONLY | O_CLOEXEC); + for (auto &it : table) { + char file_name[40]; + if (it.ramdisk_name[0] == '\0') { + strscpy(file_name, RAMDISK_FILE, sizeof(file_name)); + } else { + ssprintf(file_name, sizeof(file_name), "%s.cpio", it.ramdisk_name); + } + owned_fd fd = xopenat(dirfd, file_name, O_CREAT | O_TRUNC | O_WRONLY | O_CLOEXEC, 0644); + format_t fmt = check_fmt_lg(boot.ramdisk + it.ramdisk_offset, it.ramdisk_size); + if (!skip_decomp && COMPRESSED(fmt)) { + decompress(fmt, fd, boot.ramdisk + it.ramdisk_offset, it.ramdisk_size); + } else { + xwrite(fd, boot.ramdisk + it.ramdisk_offset, it.ramdisk_size); + } + } + } else if (!skip_decomp && COMPRESSED(boot.r_fmt)) { if (boot.hdr->ramdisk_size() != 0) { int fd = creat(RAMDISK_FILE, 0644); decompress(boot.r_fmt, fd, boot.ramdisk, boot.hdr->ramdisk_size()); @@ -591,6 +634,9 @@ int unpack(const char *image, bool skip_decomp, bool hdr) { // Dump dtb dump(boot.dtb, boot.hdr->dtb_size(), DTB_FILE); + // Dump bootconfig + dump(boot.bootconfig, boot.hdr->bootconfig_size(), BOOTCONFIG_FILE); + return boot.flags[CHROMEOS_FLAG] ? 2 : 0; } @@ -620,6 +666,7 @@ void repack(const char *src_img, const char *out_img, bool skip_comp) { hdr->ramdisk_size() = 0; hdr->second_size() = 0; hdr->dtb_size() = 0; + hdr->bootconfig_size() = 0; if (access(HEADER_FILE, R_OK) == 0) hdr->load_hdr_file(); @@ -703,7 +750,40 @@ void repack(const char *src_img, const char *out_img, bool skip_comp) { // Copy MTK headers xwrite(fd, boot.r_hdr, sizeof(mtk_hdr)); } - if (access(RAMDISK_FILE, R_OK) == 0) { + + using table_entry = vendor_ramdisk_table_entry_v4; + vector ramdisk_table; + + if (boot.vendor_ramdisk_table) { + // Create a copy so we can modify it + auto entry_start = reinterpret_cast(boot.vendor_ramdisk_table); + ramdisk_table.insert( + ramdisk_table.begin(), + entry_start, entry_start + boot.hdr->vendor_ramdisk_table_entry_num()); + + owned_fd dirfd = xopen(VND_RAMDISK_DIR, O_RDONLY | O_CLOEXEC); + uint32_t ramdisk_offset = 0; + for (auto &it : ramdisk_table) { + char file_name[64]; + if (it.ramdisk_name[0] == '\0') { + strscpy(file_name, RAMDISK_FILE, sizeof(file_name)); + } else { + ssprintf(file_name, sizeof(file_name), "%s.cpio", it.ramdisk_name); + } + mmap_data m(dirfd, file_name); + format_t fmt = check_fmt_lg(boot.ramdisk + it.ramdisk_offset, it.ramdisk_size); + it.ramdisk_offset = ramdisk_offset; + if (!skip_comp && !COMPRESSED_ANY(check_fmt(m.buf(), m.sz())) && COMPRESSED(fmt)) { + it.ramdisk_size = compress(fmt, fd, m.buf(), m.sz()); + } else { + it.ramdisk_size = xwrite(fd, m.buf(), m.sz()); + } + ramdisk_offset += it.ramdisk_size; + } + + hdr->ramdisk_size() = ramdisk_offset; + file_align(); + } else if (access(RAMDISK_FILE, R_OK) == 0) { mmap_data m(RAMDISK_FILE); auto r_fmt = boot.r_fmt; if (!skip_comp && !hdr->is_vendor() && hdr->header_version() == 4 && r_fmt != LZ4_LEGACY) { @@ -754,10 +834,22 @@ void repack(const char *src_img, const char *out_img, bool skip_comp) { file_align(); } - // Directly copy ignored blobs - if (boot.ignore.sz()) { - // ignore.sz() should already be aligned - xwrite(fd, boot.ignore.buf(), boot.ignore.sz()); + // Copy boot signature + if (boot.hdr->signature_size()) { + xwrite(fd, boot.signature, boot.hdr->signature_size()); + file_align(); + } + + // vendor ramdisk table + if (!ramdisk_table.empty()) { + xwrite(fd, ramdisk_table.data(), sizeof(table_entry) * ramdisk_table.size()); + file_align(); + } + + // bootconfig + if (access(BOOTCONFIG_FILE, R_OK) == 0) { + hdr->bootconfig_size() = restore(fd, BOOTCONFIG_FILE); + file_align(); } // Proprietary stuffs diff --git a/native/src/boot/bootimg.hpp b/native/src/boot/bootimg.hpp index a8e078e64..2e185bc94 100644 --- a/native/src/boot/bootimg.hpp +++ b/native/src/boot/bootimg.hpp @@ -1,6 +1,6 @@ #pragma once -#include +#include #include #include #include @@ -111,15 +111,21 @@ struct AvbVBMetaImageHeader { #define VENDOR_RAMDISK_NAME_SIZE 32 #define VENDOR_RAMDISK_TABLE_ENTRY_BOARD_ID_SIZE 16 -/* When the boot image header has a version of 0 - 2, the structure of the boot +#define VENDOR_RAMDISK_TYPE_NONE 0 +#define VENDOR_RAMDISK_TYPE_PLATFORM 1 +#define VENDOR_RAMDISK_TYPE_RECOVERY 2 +#define VENDOR_RAMDISK_TYPE_DLKM 3 + +/* + * When the boot image header has a version of 0 - 2, the structure of the boot * image is as follows: * * +-----------------+ * | boot header | 1 page * +-----------------+ - * | kernel | n pages + * | kernel | m pages * +-----------------+ - * | ramdisk | m pages + * | ramdisk | n pages * +-----------------+ * | second stage | o pages * +-----------------+ @@ -130,8 +136,8 @@ struct AvbVBMetaImageHeader { * | dtb | q pages * +-----------------+ * - * n = (kernel_size + page_size - 1) / page_size - * m = (ramdisk_size + page_size - 1) / page_size + * m = (kernel_size + page_size - 1) / page_size + * n = (ramdisk_size + page_size - 1) / page_size * o = (second_size + page_size - 1) / page_size * p = (recovery_dtbo_size + page_size - 1) / page_size * q = (dtb_size + page_size - 1) / page_size @@ -211,7 +217,8 @@ struct boot_img_hdr_pxa : public boot_img_hdr_v0_common { char extra_cmdline[BOOT_EXTRA_ARGS_SIZE]; } __attribute__((packed)); -/* When the boot image header has a version of 3 - 4, the structure of the boot +/* + * When the boot image header has a version of 3 - 4, the structure of the boot * image is as follows: * * +---------------------+ @@ -329,7 +336,7 @@ struct vendor_ramdisk_table_entry_v4 { uint32_t ramdisk_size; /* size in bytes for the ramdisk image */ uint32_t ramdisk_offset; /* offset to the ramdisk image in vendor ramdisk section */ uint32_t ramdisk_type; /* type of the ramdisk */ - uint8_t ramdisk_name[VENDOR_RAMDISK_NAME_SIZE]; /* asciiz ramdisk name */ + char ramdisk_name[VENDOR_RAMDISK_NAME_SIZE]; /* asciiz ramdisk name */ // Hardware identifiers describing the board, soc or platform which this // ramdisk is intended to be loaded on. @@ -376,8 +383,12 @@ struct dyn_img_hdr { // v4 specific decl_val(signature_size, 32) + + // v4 vendor specific decl_val(vendor_ramdisk_table_size, 32) - decl_val(bootconfig_size, 32) + decl_val(vendor_ramdisk_table_entry_num, 32) + decl_val(vendor_ramdisk_table_entry_size, 32) + decl_var(bootconfig_size, 32) virtual ~dyn_img_hdr() { free(raw); @@ -554,7 +565,9 @@ struct dyn_img_vnd_v4 : public dyn_img_vnd_v3 { impl_cls(vnd_v4) impl_val(vendor_ramdisk_table_size) - impl_val(bootconfig_size) + impl_val(vendor_ramdisk_table_entry_num) + impl_val(vendor_ramdisk_table_entry_size) + impl_var(bootconfig_size) }; #undef __impl_cls @@ -591,7 +604,7 @@ struct boot_img { const mmap_data map; // Android image header - const dyn_img_hdr *hdr; + const dyn_img_hdr *hdr = nullptr; // Flags to indicate the state of current boot image std::bitset flags; @@ -607,9 +620,9 @@ struct boot_img { // Layout of the memory mapped region // +---------+ - // | head | Vendor specific. Should be empty for standard AOSP boot images. + // | head | Vendor specific. Should not exist for standard AOSP boot images. // +---------+ - // | payload | The actual boot image, including the AOSP boot image header. + // | payload | The actual entire AOSP boot image, including the boot image header. // +---------+ // | tail | Data after payload. Usually contains signature/AVB information. // +---------+ @@ -618,8 +631,8 @@ struct boot_img { byte_view tail; // MTK headers - const mtk_hdr *k_hdr; - const mtk_hdr *r_hdr; + const mtk_hdr *k_hdr = nullptr; + const mtk_hdr *r_hdr = nullptr; // The pointers/values after parse_image // +---------------+ @@ -629,23 +642,26 @@ struct boot_img { // +---------------+ // | z_info.tail | z_info.tail.sz() // +---------------+ - const zimage_hdr *z_hdr; + const zimage_hdr *z_hdr = nullptr; struct { uint32_t hdr_sz; byte_view tail; } z_info; // AVB structs - const AvbFooter *avb_footer; - const AvbVBMetaImageHeader *vbmeta; + const AvbFooter *avb_footer = nullptr; + const AvbVBMetaImageHeader *vbmeta = nullptr; // Pointers to blocks defined in header - const uint8_t *kernel; - const uint8_t *ramdisk; - const uint8_t *second; - const uint8_t *extra; - const uint8_t *recovery_dtbo; - const uint8_t *dtb; + const uint8_t *kernel = nullptr; + const uint8_t *ramdisk = nullptr; + const uint8_t *second = nullptr; + const uint8_t *extra = nullptr; + const uint8_t *recovery_dtbo = nullptr; + const uint8_t *dtb = nullptr; + const uint8_t *signature = nullptr; + const uint8_t *vendor_ramdisk_table = nullptr; + const uint8_t *bootconfig = nullptr; // dtb embedded in kernel byte_view kernel_dtb; @@ -657,7 +673,7 @@ struct boot_img { ~boot_img(); bool parse_image(const uint8_t *addr, format_t type); - const std::pair create_hdr(const uint8_t *addr, format_t type); + std::pair create_hdr(const uint8_t *addr, format_t type); // Rust FFI rust::Slice get_payload() const { return payload; } diff --git a/native/src/boot/magiskboot.hpp b/native/src/boot/magiskboot.hpp index 8712428ed..f590a740b 100644 --- a/native/src/boot/magiskboot.hpp +++ b/native/src/boot/magiskboot.hpp @@ -7,11 +7,13 @@ #define HEADER_FILE "header" #define KERNEL_FILE "kernel" #define RAMDISK_FILE "ramdisk.cpio" +#define VND_RAMDISK_DIR "vendor_ramdisk" #define SECOND_FILE "second" #define EXTRA_FILE "extra" #define KER_DTB_FILE "kernel_dtb" #define RECV_DTBO_FILE "recovery_dtbo" #define DTB_FILE "dtb" +#define BOOTCONFIG_FILE "bootconfig" #define NEW_BOOT "new-boot.img" int unpack(const char *image, bool skip_decomp = false, bool hdr = false);