diff --git a/CMakeLists.txt b/CMakeLists.txt index 69956105..cd21eaf9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -416,6 +416,8 @@ set(INNOEXTRACT_SOURCES src/setup/info.cpp src/setup/ini.hpp src/setup/ini.cpp + src/setup/issigkey.hpp + src/setup/issigkey.cpp src/setup/item.hpp src/setup/item.cpp src/setup/language.hpp diff --git a/src/cli/debug.cpp b/src/cli/debug.cpp index c4e4cd36..78d1c358 100644 --- a/src/cli/debug.cpp +++ b/src/cli/debug.cpp @@ -652,7 +652,7 @@ static void dump_headers(std::istream & is, const setup::version & version, cons void dump_headers(std::istream & is, const loader::offsets & offsets, const extract_options & o) { setup::version version; - is.seekg(offsets.header_offset); + is.seekg(std::streamoff(offsets.header_offset)); version.load(is); dump_headers(is, version, o, 0); diff --git a/src/cli/extract.cpp b/src/cli/extract.cpp index 49893117..ccff1bbd 100644 --- a/src/cli/extract.cpp +++ b/src/cli/extract.cpp @@ -946,7 +946,7 @@ void process_file(const fs::path & installer, const extract_options & o) { if(o.data_version) { setup::version version; - ifs.seekg(offsets.header_offset); + ifs.seekg(std::streamoff(offsets.header_offset)); version.load(ifs); if(o.silent) { std::cout << version << '\n'; @@ -985,7 +985,7 @@ void process_file(const fs::path & installer, const extract_options & o) { } #endif - ifs.seekg(offsets.header_offset); + ifs.seekg(std::streamoff(offsets.header_offset)); setup::info info; try { info.load(ifs, entries, o.codepage); @@ -1100,7 +1100,7 @@ void process_file(const fs::path & installer, const extract_options & o) { if(o.test || o.extract) { boost::uint64_t offset = info.data_entries[file.entry().location].uncompressed_size; boost::uint32_t sort_slice = info.data_entries[file.entry().location].chunk.first_slice; - boost::uint32_t sort_offset = info.data_entries[file.entry().location].chunk.sort_offset; + boost::uint64_t sort_offset = info.data_entries[file.entry().location].chunk.sort_offset; BOOST_FOREACH(boost::uint32_t location, file.entry().additional_locations) { setup::data_entry & data = info.data_entries[location]; files_for_location[location].push_back(output_location(&file, offset)); diff --git a/src/loader/offsets.cpp b/src/loader/offsets.cpp index 0c0d6a44..2e30a447 100644 --- a/src/loader/offsets.cpp +++ b/src/loader/offsets.cpp @@ -109,9 +109,9 @@ bool offsets::load_from_exe_resource(std::istream & is) { return load_offsets_at(is, resource.offset); } -bool offsets::load_offsets_at(std::istream & is, boost::uint32_t pos) { +bool offsets::load_offsets_at(std::istream & is, boost::uint64_t pos) { - if(is.seekg(pos).fail()) { + if(is.seekg(std::streamoff(pos)).fail()) { is.clear(); debug("could not seek to loader header"); return false; @@ -148,6 +148,51 @@ bool offsets::load_offsets_at(std::istream & is, boost::uint32_t pos) { is.clear(); debug("could not read loader header revision"); return false; + } else if(revision == 2) { + /* + * Inno Setup 6.5.0 introduced a wider TSetupLdrOffsetTable layout + * (revision 2). The fields previously stored as 32-bit LongWord + * are now Int64, allowing setup binaries larger than 4 GiB. A new + * ReservedPadding UInt32 sits between Offset1 and TableCRC. The + * record itself is no longer Delphi-`packed`, but with 32-bit + * Delphi default alignment the on-disk layout remains contiguous + * (Int64 has 4-byte alignment in 32-bit mode), so the total size + * goes from 48 to 64 bytes. See Projects/Src/Shared.Struct.pas in + * issrc. + */ + (void)checksum.load(is); // TotalSize + boost::int64_t offset_exe = checksum.load(is); + boost::uint32_t uncompressed_exe = checksum.load(is); + boost::uint32_t crc_exe = checksum.load(is); + boost::int64_t offset0 = checksum.load(is); + boost::int64_t offset1 = checksum.load(is); + (void)checksum.load(is); // ReservedPadding + if(is.fail()) { + is.clear(); + debug("could not read loader header (revision 2)"); + return false; + } + if(offset_exe < 0 || offset0 < 0 || offset1 < 0) { + log_warning << "Loader header has negative offset(s)"; + } + exe_offset = boost::uint64_t(offset_exe); + exe_compressed_size = 0; + exe_uncompressed_size = uncompressed_exe; + exe_checksum.type = crypto::CRC32; + exe_checksum.crc32 = crc_exe; + message_offset = 0; + header_offset = boost::uint64_t(offset0); + data_offset = boost::uint64_t(offset1); + boost::uint32_t expected = util::load(is); + if(is.fail()) { + is.clear(); + debug("could not read loader header checksum (revision 2)"); + return false; + } + if(checksum.finalize() != expected) { + log_warning << "Setup loader checksum mismatch!"; + } + return true; } else if(revision != 1) { log_warning << "Unexpected setup loader revision: " << revision; } diff --git a/src/loader/offsets.hpp b/src/loader/offsets.hpp index f49bd918..cefefb2a 100644 --- a/src/loader/offsets.hpp +++ b/src/loader/offsets.hpp @@ -60,17 +60,17 @@ struct offsets { * * A value of \c 0 means there is no setup.e32 embedded in this file */ - boost::uint32_t exe_offset; + boost::uint64_t exe_offset; /*! * Size of `setup.e32` after compression, in bytes * * A value of \c 0 means the executable size is not known */ - boost::uint32_t exe_compressed_size; + boost::uint64_t exe_compressed_size; //! Size of `setup.e32` before compression, in bytes - boost::uint32_t exe_uncompressed_size; + boost::uint64_t exe_uncompressed_size; /*! * Checksum of `setup.e32` before compression @@ -80,7 +80,7 @@ struct offsets { crypto::checksum exe_checksum; //! Offset of embedded setup messages - boost::uint32_t message_offset; + boost::uint64_t message_offset; /*! * Offset of embedded `setup-0.bin` data (the setup headers) @@ -93,7 +93,7 @@ struct offsets { * * Loading the version and headers is done in \ref setup::info. */ - boost::uint32_t header_offset; + boost::uint64_t header_offset; /*! * Offset of embedded `setup-1.bin` data @@ -109,7 +109,7 @@ struct offsets { * The layout of the chunks and files is stored in the \ref setup::data_entry headers * while the \ref setup::file_entry headers provide the filenames and meta information. */ - boost::uint32_t data_offset; + boost::uint64_t data_offset; /*! * \brief Find the setup loader offsets in a file @@ -128,7 +128,7 @@ struct offsets { bool load_from_exe_resource(std::istream & is); - bool load_offsets_at(std::istream & is, boost::uint32_t pos); + bool load_offsets_at(std::istream & is, boost::uint64_t pos); }; diff --git a/src/setup/component.cpp b/src/setup/component.cpp index eab4f1a5..b6d3734d 100644 --- a/src/setup/component.cpp +++ b/src/setup/component.cpp @@ -74,7 +74,13 @@ void component_entry::load(std::istream & is, const info & i) { } else { extra_disk_pace_required = util::load(is); } - if(i.version >= INNO_VERSION(4, 0, 0) || (i.version.is_isx() && i.version >= INNO_VERSION(3, 0, 3))) { + if(i.version >= INNO_VERSION(6, 7, 0)) { + // Inno Setup 6.7.0 narrowed TSetupComponentEntry.Level from + // Integer (4 bytes) to Byte (1 byte). See + // Projects/Src/Shared.Struct.pas at tag is-6_7_0 in + // https://github.com/jrsoftware/issrc. + level = util::load(is); + } else if(i.version >= INNO_VERSION(4, 0, 0) || (i.version.is_isx() && i.version >= INNO_VERSION(3, 0, 3))) { level = util::load(is); } else { level = 0; diff --git a/src/setup/data.cpp b/src/setup/data.cpp index 4444ce29..9e48939c 100644 --- a/src/setup/data.cpp +++ b/src/setup/data.cpp @@ -56,7 +56,16 @@ void data_entry::load(std::istream & is, const info & i) { } } - chunk.sort_offset = chunk.offset = util::load(is); + if(i.version >= INNO_VERSION(6, 5, 2)) { + // Inno Setup 6.5.2 widened TSetupFileLocationEntry.StartOffset + // from LongWord (4 bytes) to Int64 (8 bytes) so data offsets + // within a slice can exceed 4 GiB. See + // Projects/Src/Shared.Struct.pas at tag is-6_5_2 in + // https://github.com/jrsoftware/issrc. + chunk.sort_offset = chunk.offset = util::load(is); + } else { + chunk.sort_offset = chunk.offset = util::load(is); + } if(i.version >= INNO_VERSION(4, 0, 1)) { file.offset = util::load(is); @@ -136,20 +145,28 @@ void data_entry::load(std::istream & is, const info & i) { stored_flag_reader flagreader(is, i.version.bits()); flagreader.add(VersionInfoValid); - flagreader.add(VersionInfoNotValid); + if(i.version < INNO_VERSION(6, 4, 3)) { + // Inno Setup 6.4.3 dropped foVersionInfoNotValid, foIsUninstExe, + // foApplyTouchDateTime and foSolidBreak from + // TSetupFileLocationEntry.Flags. See + // Projects/Src/Shared.Struct.pas at tag is-6_4_3 in + // https://github.com/jrsoftware/issrc (issrc commit 6aec0a55, + // "Distinguish file options (fo) and file location options (flo)."). + flagreader.add(VersionInfoNotValid); + } if(i.version >= INNO_VERSION(2, 0, 17) && i.version < INNO_VERSION(4, 0, 1)) { flagreader.add(BZipped); } if(i.version >= INNO_VERSION(4, 0, 10)) { flagreader.add(TimeStampInUTC); } - if(i.version >= INNO_VERSION(4, 1, 0)) { + if(i.version >= INNO_VERSION(4, 1, 0) && i.version < INNO_VERSION(6, 4, 3)) { flagreader.add(IsUninstallerExe); } if(i.version >= INNO_VERSION(4, 1, 8)) { flagreader.add(CallInstructionOptimized); } - if(i.version >= INNO_VERSION(4, 2, 0)) { + if(i.version >= INNO_VERSION(4, 2, 0) && i.version < INNO_VERSION(6, 4, 3)) { flagreader.add(Touch); } if(i.version >= INNO_VERSION(4, 2, 2)) { @@ -160,7 +177,7 @@ void data_entry::load(std::istream & is, const info & i) { } else { options |= ChunkCompressed; } - if(i.version >= INNO_VERSION(5, 1, 13)) { + if(i.version >= INNO_VERSION(5, 1, 13) && i.version < INNO_VERSION(6, 4, 3)) { flagreader.add(SolidBreak); } if(i.version >= INNO_VERSION(5, 5, 7) && i.version < INNO_VERSION(6, 3, 0)) { @@ -171,7 +188,16 @@ void data_entry::load(std::istream & is, const info & i) { options |= flagreader.finalize(); - if(i.version >= INNO_VERSION(6, 3, 0)) { + if(i.version >= INNO_VERSION(6, 4, 3)) { + // Inno Setup 6.4.3 dropped the standalone Sign field from + // TSetupFileLocationEntry. The compiler still records the source + // signing intent in the [Files] section, but it is no longer + // serialised per-file-location. See issrc commit 00d335b7 + // ("Cleanup: TSetupFileLocationEntry contained a few things which + // Setup doesn't need and are only for the compiler.") first + // tagged in is-6_4_3. + sign = NoSetting; + } else if(i.version >= INNO_VERSION(6, 3, 0)) { sign = stored_enum(is).get(); } else if(options & SignOnce) { sign = Once; diff --git a/src/setup/file.cpp b/src/setup/file.cpp index a2e917e3..484b0272 100644 --- a/src/setup/file.cpp +++ b/src/setup/file.cpp @@ -56,6 +56,14 @@ STORED_ENUM_MAP(stored_file_type_1, file_entry::UserFile, file_entry::RegSvrExe, ); +// Inno Setup 6.5.0 introduced the per-file verification kind alongside +// the SHA-256 hash and ISSig allowed-keys list appended to TSetupFileEntry. +STORED_ENUM_MAP(stored_file_verification_type, file_entry::FileVerificationNone, + file_entry::FileVerificationNone, + file_entry::FileVerificationHash, + file_entry::FileVerificationISSig, +); + } // anonymous namespace } // namespace setup @@ -69,6 +77,12 @@ NAMES(setup::file_copy_mode, "File Copy Mode", "always skip if same or older", ) +NAMES(setup::file_entry::file_verification_type, "File Verification Type", + "none", + "hash", + "IS sig", +) + namespace setup { void file_entry::load(std::istream & is, const info & i) { @@ -92,6 +106,34 @@ void file_entry::load(std::istream & is, const info & i) { load_condition_data(is, i); + if(i.version >= INNO_VERSION(6, 5, 0)) { + // Inno Setup 6.5.0 appended five new expression strings (Excludes, + // DownloadISSigSource, DownloadUserName, DownloadPassword, + // ExtractArchivePassword) and a per-file Verification packed + // record (ISSigAllowedKeys ansi string, 32-byte SHA-256 digest, + // TSetupFileVerificationType byte enum) to TSetupFileEntry, after + // the BeforeInstall string and before MinVersion. See + // Projects/Src/Shared.Struct.pas at tag is-6_5_0 in + // https://github.com/jrsoftware/issrc. + is >> util::encoded_string(excludes, i.codepage, i.header.lead_bytes); + is >> util::encoded_string(download_source, i.codepage, i.header.lead_bytes); + is >> util::encoded_string(download_user, i.codepage, i.header.lead_bytes); + is >> util::encoded_string(download_password, i.codepage, i.header.lead_bytes); + is >> util::encoded_string(archive_password, i.codepage, i.header.lead_bytes); + is >> util::ansi_string(issig_allowed_keys); + is.read(checksum.sha256, std::streamsize(sizeof(checksum.sha256))); + checksum.type = crypto::SHA256; + verification = stored_enum(is).get(); + } else { + excludes.clear(); + download_source.clear(); + download_user.clear(); + download_password.clear(); + archive_password.clear(); + issig_allowed_keys.clear(); + verification = FileVerificationNone; + } + load_version_data(is, i.version); location = util::load(is, i.version.bits()); @@ -189,8 +231,25 @@ void file_entry::load(std::istream & is, const info & i) { if(i.version >= INNO_VERSION(5, 2, 5)) { flagreader.add(GacInstall); } + if(i.version >= INNO_VERSION(6, 5, 0)) { + // Inno Setup 6.5.0 added two flags at the end of the bitset for + // the new [Files] flags `download` and `extractarchive` + // (TSetupFileEntry.Options in Projects/Src/Shared.Struct.pas at + // tag is-6_5_0 in https://github.com/jrsoftware/issrc). + flagreader.add(Download); + flagreader.add(ExtractArchive); + } options |= flagreader.finalize(); + if(i.version >= INNO_VERSION(6, 7, 0)) { + // Inno Setup 6.7.0 padded TSetupFileEntryOption to 57 elements + // (foUnusedPadding=56) so the set is always 8 bytes regardless + // of the actual flag count, matching the analogous change to + // TSetupHeaderOption (see is-6_7_0 in issrc). Skip past the + // extra padding bytes so the trailing FileType byte is read + // from the right offset. + flagreader.discard_padding_to(8); + } if(i.version.bits() == 16 || i.version >= INNO_VERSION(5, 0, 0)) { type = stored_enum(is).get(); @@ -199,7 +258,11 @@ void file_entry::load(std::istream & is, const info & i) { } additional_locations.clear(); - checksum.type = crypto::None; + if(i.version < INNO_VERSION(6, 5, 0)) { + // For Inno Setup 6.5.0+, file_entry::checksum carries the SHA-256 + // from the per-file ISSig Verification block, populated above. + checksum.type = crypto::None; + } size = 0; } @@ -239,6 +302,8 @@ NAMES(setup::file_entry::flags, "File Option", "set ntfs compression", "unset ntfs compression", "gac install", + "download", + "extract archive", "readme", ) diff --git a/src/setup/file.hpp b/src/setup/file.hpp index 604b2ae8..9f306bfb 100644 --- a/src/setup/file.hpp +++ b/src/setup/file.hpp @@ -77,11 +77,19 @@ struct file_entry : public item { SetNtfsCompression, UnsetNtfsCompression, GacInstall, + Download, + ExtractArchive, // obsolete options: IsReadmeFile ); + enum file_verification_type { + FileVerificationNone, + FileVerificationHash, + FileVerificationISSig, + }; + enum file_type { UserFile, UninstExe, @@ -96,6 +104,14 @@ struct file_entry : public item { std::string destination; std::string install_font_name; std::string strong_assembly_name; + std::string excludes; //!< Inno Setup 6.5.0+ + std::string download_source; //!< DownloadISSigSource, 6.5.0+ + std::string download_user; //!< DownloadUserName, 6.5.0+ + std::string download_password; //!< 6.5.0+ + std::string archive_password; //!< ExtractArchivePassword, 6.5.0+ + std::string issig_allowed_keys; //!< 6.5.0+ + + file_verification_type verification; //!< 6.5.0+ boost::uint32_t location; //!< index into the data entry list boost::uint32_t attributes; @@ -110,7 +126,7 @@ struct file_entry : public item { // Information about GOG Galaxy multi-part files // These are not used in normal Inno Setup installers std::vector additional_locations; - crypto::checksum checksum; + crypto::checksum checksum; //!< Inno Setup 6.5.0+: SHA-256 from the per-file ISSig Verification block boost::uint64_t size; void load(std::istream & is, const info & i); @@ -121,5 +137,6 @@ struct file_entry : public item { NAMED_FLAGS(setup::file_entry::flags) NAMED_ENUM(setup::file_entry::file_type) +NAMED_ENUM(setup::file_entry::file_verification_type) #endif // INNOEXTRACT_SETUP_FILE_HPP diff --git a/src/setup/header.cpp b/src/setup/header.cpp index bbe9df9c..7fbac6fc 100644 --- a/src/setup/header.cpp +++ b/src/setup/header.cpp @@ -56,6 +56,18 @@ STORED_ENUM_MAP(stored_setup_style, header::ClassicStyle, header::ModernStyle ); +STORED_ENUM_MAP(stored_dark_style, header::LightStyle, + header::LightStyle, + header::DarkStyle, + header::DynamicStyle +); + +STORED_ENUM_MAP(stored_light_control_styling, header::LightControlStyleAll, + header::LightControlStyleAll, + header::LightControlStyleAllButButtons, + header::LightControlStyleOnlyRequired +); + STORED_ENUM_MAP(stored_bool_auto_no_yes, header::Auto, header::Auto, header::No, @@ -266,6 +278,34 @@ void header::load(std::istream & is, const version & version) { is >> util::binary_string(architectures_allowed_expr); is >> util::binary_string(architectures_installed_in_64bit_mode_expr); } + if(version >= INNO_VERSION(6, 4, 2)) { + is >> util::binary_string(close_applications_filter_excludes); + } else { + close_applications_filter_excludes.clear(); + } + if(version >= INNO_VERSION(6, 5, 0)) { + is >> util::binary_string(seven_zip_library_name); + } else { + seven_zip_library_name.clear(); + } + if(version >= INNO_VERSION(6, 7, 0)) { + // Inno Setup 6.7.0 moved five settings out of the + // TSetupHeaderOption bitset into expression-string fields: + // UsePreviousAppDir, UsePreviousGroup, UsePreviousSetupType, + // UsePreviousTasks and UsePreviousUserInfo + // (Projects/Src/Shared.Struct.pas at tag is-6_7_0). + is >> util::binary_string(use_previous_app_dir); + is >> util::binary_string(use_previous_group); + is >> util::binary_string(use_previous_setup_type); + is >> util::binary_string(use_previous_tasks); + is >> util::binary_string(use_previous_user_info); + } else { + use_previous_app_dir.clear(); + use_previous_group.clear(); + use_previous_setup_type.clear(); + use_previous_tasks.clear(); + use_previous_user_info.clear(); + } if(version >= INNO_VERSION(5, 2, 5)) { is >> util::ansi_string(license_text); is >> util::ansi_string(info_before); @@ -319,6 +359,18 @@ void header::load(std::istream & is, const version & version) { } directory_count = util::load(is, version.bits()); + if(version >= INNO_VERSION(6, 5, 0)) { + // Inno Setup 6.5.0 inserted a NumISSigKeyEntries: Integer field + // between NumDirEntries and NumFileEntries + // (Projects/Src/Shared.Struct.pas at tag is-6_5_0). Each entry is a + // triple of binary strings (PublicX, PublicY, RuntimeID) describing + // an ISSigTool-issued public key the installer trusts; innoextract + // does not yet make use of the keys themselves but stores the count + // so info::try_load can skip the right number of entries. + issig_key_count = util::load(is, version.bits()); + } else { + issig_key_count = 0; + } file_count = util::load(is, version.bits()); data_entry_count = util::load(is, version.bits()); icon_count = util::load(is, version.bits()); @@ -361,14 +413,25 @@ void header::load(std::istream & is, const version & version) { small_image_back_color = 0; } - if(version >= INNO_VERSION(6, 0, 0)) { + if(version >= INNO_VERSION(6, 6, 0)) { + // Inno Setup 6.6.0 removed WizardStyle (the classic/modern toggle is + // gone) and added WizardDarkStyle (light/dark/dynamic) in its place, + // after the WizardSizePercentX/Y pair instead of before it. See + // TSetupHeader in Projects/Src/Shared.Struct.pas at tag is-6_6_0. + wizard_style = ModernStyle; + wizard_resize_percent_x = util::load(is); + wizard_resize_percent_y = util::load(is); + wizard_dark_style = stored_enum(is).get(); + } else if(version >= INNO_VERSION(6, 0, 0)) { wizard_style = stored_enum(is).get(); wizard_resize_percent_x = util::load(is); wizard_resize_percent_y = util::load(is); + wizard_dark_style = LightStyle; } else { wizard_style = ClassicStyle; wizard_resize_percent_x = 0; wizard_resize_percent_y = 0; + wizard_dark_style = LightStyle; } if(version >= INNO_VERSION(5, 5, 7)) { @@ -377,7 +440,64 @@ void header::load(std::istream & is, const version & version) { image_alpha_format = AlphaIgnored; } - if(version >= INNO_VERSION(6, 4, 0)) { + if(version >= INNO_VERSION(6, 5, 2)) { + // Inno Setup 6.5.2 re-introduced WizardImageBackColor and + // WizardSmallImageBackColor as serialised header fields after + // WizardImageAlphaFormat; before 5.5.7 they sat much earlier + // in the struct (handled above). + image_back_color = util::load(is); + small_image_back_color = util::load(is); + } + if(version >= INNO_VERSION(6, 7, 0)) { + // 6.7.0 inserted WizardBackColor between WizardSmallImageBackColor + // and the *DynamicDark colour pair. + wizard_back_color = util::load(is); + } else { + wizard_back_color = 0; + } + if(version >= INNO_VERSION(6, 6, 0)) { + // 6.6.0 added the DynamicDark variants. + image_back_color_dynamic_dark = util::load(is); + small_image_back_color_dynamic_dark = util::load(is); + } else { + image_back_color_dynamic_dark = 0; + small_image_back_color_dynamic_dark = 0; + } + if(version >= INNO_VERSION(6, 7, 0)) { + // 6.7.0 also added WizardBackColorDynamicDark, in the same + // position relative to its dynamic-dark companions. + wizard_back_color_dynamic_dark = util::load(is); + } else { + wizard_back_color_dynamic_dark = 0; + } + if(version >= INNO_VERSION(6, 6, 1)) { + // 6.6.1 added WizardImageOpacity (Byte). Default for older versions + // is fully opaque. + wizard_image_opacity = util::load(is); + } else { + wizard_image_opacity = 0xff; + } + if(version >= INNO_VERSION(6, 7, 0)) { + // 6.7.0 added WizardBackImageOpacity (Byte) and + // WizardLightControlStyling (TSetupWizardLightControlStyling, byte + // enum: wcsAll / wcsAllButButtons / wcsOnlyRequired). + wizard_back_image_opacity = util::load(is); + wizard_light_control_styling = stored_enum(is).get(); + } else { + wizard_back_image_opacity = 0xff; + wizard_light_control_styling = LightControlStyleAll; + } + + if(version >= INNO_VERSION(6, 5, 0)) { + // Inno Setup 6.5.0 moved the encryption fields (password test, KDF + // salt, KDF iterations, base nonce) out of TSetupHeader and into a + // separate TSetupEncryptionHeader stored elsewhere. innoextract + // does not yet parse encrypted 6.5.0+ installers; for unencrypted + // ones the right thing is to read no encryption bytes from the + // header stream at all. + password.type = crypto::None; + password_salt.clear(); + } else if(version >= INNO_VERSION(6, 4, 0)) { is.read(password.sha256, 4); password.type = crypto::PBKDF2_SHA256_XChaCha20; } else if(version >= INNO_VERSION(5, 3, 9)) { @@ -390,7 +510,9 @@ void header::load(std::istream & is, const version & version) { password.crc32 = util::load(is); password.type = crypto::CRC32; } - if(version >= INNO_VERSION(6, 4, 0)) { + if(version >= INNO_VERSION(6, 5, 0)) { + // Already cleared above; nothing to read. + } else if(version >= INNO_VERSION(6, 4, 0)) { password_salt.resize(44); // PBKDF2 salt + iteration count + ChaCha2 base nonce is.read(&password_salt[0], std::streamsize(password_salt.length())); } else if(version >= INNO_VERSION(4, 2, 2)) { @@ -626,19 +748,20 @@ header::flags header::load_flags(std::istream & is, const version & version) { if(version >= INNO_VERSION(1, 3, 0) && version < INNO_VERSION(5, 3, 8)) { flagreader.add(CreateUninstallRegKey); } - if(version >= INNO_VERSION(1, 3, 1)) { + if(version >= INNO_VERSION(1, 3, 1) && version < INNO_VERSION(6, 7, 0)) { flagreader.add(UsePreviousAppDir); } if(version >= INNO_VERSION(1, 3, 3) && version < INNO_VERSION_EXT(6, 4, 0, 1)) { flagreader.add(BackColorHorizontal); } - if(version >= INNO_VERSION(1, 3, 10)) { + if(version >= INNO_VERSION(1, 3, 10) && version < INNO_VERSION(6, 7, 0)) { flagreader.add(UsePreviousGroup); } if(version >= INNO_VERSION(1, 3, 20)) { flagreader.add(UpdateUninstallLogAppName); } - if(version >= INNO_VERSION(2, 0, 0) || (version.is_isx() && version >= INNO_VERSION(1, 3, 10))) { + if((version >= INNO_VERSION(2, 0, 0) || (version.is_isx() && version >= INNO_VERSION(1, 3, 10))) + && version < INNO_VERSION(6, 7, 0)) { flagreader.add(UsePreviousSetupType); } if(version >= INNO_VERSION(2, 0, 0)) { @@ -646,7 +769,9 @@ header::flags header::load_flags(std::istream & is, const version & version) { flagreader.add(AlwaysShowComponentsList); flagreader.add(FlatComponentsList); flagreader.add(ShowComponentSizes); - flagreader.add(UsePreviousTasks); + if(version < INNO_VERSION(6, 7, 0)) { + flagreader.add(UsePreviousTasks); + } flagreader.add(DisableReadyPage); } if(version >= INNO_VERSION(2, 0, 7)) { @@ -661,7 +786,9 @@ header::flags header::load_flags(std::istream & is, const version & version) { } if(version >= INNO_VERSION(3, 0, 0)) { flagreader.add(UserInfoPage); - flagreader.add(UsePreviousUserInfo); + if(version < INNO_VERSION(6, 7, 0)) { + flagreader.add(UsePreviousUserInfo); + } } if(version >= INNO_VERSION(3, 0, 1)) { flagreader.add(UninstallRestartComputer); @@ -690,7 +817,7 @@ header::flags header::load_flags(std::istream & is, const version & version) { flagreader.add(AppendDefaultDirName); flagreader.add(AppendDefaultGroupName); } - if(version >= INNO_VERSION(4, 2, 2)) { + if(version >= INNO_VERSION(4, 2, 2) && version < INNO_VERSION(6, 5, 0)) { flagreader.add(EncryptionUsed); } if(version >= INNO_VERSION(5, 0, 4) && version < INNO_VERSION(5, 6, 1)) { @@ -724,11 +851,49 @@ header::flags header::load_flags(std::istream & is, const version & version) { if(version >= INNO_VERSION(6, 0, 0)) { flagreader.add(AppNameHasConsts); flagreader.add(UsePreviousPrivileges); + } + if(version >= INNO_VERSION(6, 0, 0) && version < INNO_VERSION(6, 6, 0)) { + // Inno Setup 6.6.0 dropped shWizardResizable from + // TSetupHeaderOption (the wizard is unconditionally resizable in + // the new modern style; the four flags appended at the same + // position in the enum take its slot). flagreader.add(WizardResizable); } if(version >= INNO_VERSION(6, 3, 0)) { flagreader.add(UninstallLogging); } + if(version >= INNO_VERSION(6, 6, 0)) { + // Inno Setup 6.6.0 added four new wizard-styling flags after + // shUninstallLogging, three of which carry over into 6.7.0. + flagreader.add(WizardModern); + flagreader.add(WizardBorderStyled); + flagreader.add(WizardKeepAspectRatio); + } + if(version >= INNO_VERSION(6, 6, 0) && version < INNO_VERSION(6, 7, 0)) { + // shWizardLightButtonsUnstyled exists only in the 6.6.x series; + // 6.7.0 dropped it again in favour of the new + // TSetupWizardLightControlStyling enum stored further up in + // TSetupHeader. + flagreader.add(WizardLightButtonsUnstyled); + } + if(version >= INNO_VERSION(6, 7, 0)) { + // Inno Setup 6.7.0 added two more wizard-related flags at the end + // of TSetupHeaderOption; see Projects/Src/Shared.Struct.pas at + // tag is-6_7_0 in https://github.com/jrsoftware/issrc. + flagreader.add(RedirectionGuard); + flagreader.add(WizardBevelsHidden); + } + + if(version >= INNO_VERSION(6, 7, 0)) { + // Inno Setup 6.7.0 padded TSetupHeaderOption to 57 elements via a + // shUnusedPadding=56 sentinel so the set is always 8 bytes + // regardless of the actual flag count, in order to keep 32-bit + // and 64-bit Delphi builds bit-compatible (Projects/Src/Shared.Struct.pas + // at tag is-6_7_0). The flags above only fill 6 bytes; skip the + // remaining padding bytes so file_count and the rest of the + // header stay aligned. + flagreader.discard_padding_to(8); + } return flagreader.finalize(); } @@ -762,6 +927,13 @@ void header::decode(util::codepage_id codepage) { util::to_utf8(create_uninstall_registry_key, codepage, &lead_bytes); util::to_utf8(uninstallable, codepage); util::to_utf8(close_applications_filter, codepage); + util::to_utf8(close_applications_filter_excludes, codepage); + util::to_utf8(seven_zip_library_name, codepage); + util::to_utf8(use_previous_app_dir, codepage); + util::to_utf8(use_previous_group, codepage); + util::to_utf8(use_previous_setup_type, codepage); + util::to_utf8(use_previous_tasks, codepage); + util::to_utf8(use_previous_user_info, codepage); util::to_utf8(setup_mutex, codepage, &lead_bytes); util::to_utf8(changes_environment, codepage); util::to_utf8(changes_associations, codepage); @@ -823,6 +995,12 @@ NAMES(setup::header::flags, "Setup Option", "use_previous_privileges", "wizard_resizable", "uninstall_logging", + "wizard modern", + "wizard border styled", + "wizard keep aspect ratio", + "wizard light buttons unstyled", + "redirection guard", + "wizard bevels hidden", "uninstallable", "disable dir page", "disable program group page", @@ -875,6 +1053,18 @@ NAMES(setup::header::style, "Style", "modern", ) +NAMES(setup::header::dark_style, "Dark Style", + "light", + "dark", + "dynamic", +) + +NAMES(setup::header::light_control_styling, "Light Control Styling", + "all", + "all but buttons", + "only required", +) + NAMES(setup::header::auto_bool, "Auto Boolean", "auto", "no", diff --git a/src/setup/header.hpp b/src/setup/header.hpp index 78e5b64a..2138c22e 100644 --- a/src/setup/header.hpp +++ b/src/setup/header.hpp @@ -102,6 +102,12 @@ struct header { UsePreviousPrivileges, WizardResizable, UninstallLogging, + WizardModern, + WizardBorderStyled, + WizardKeepAspectRatio, + WizardLightButtonsUnstyled, + RedirectionGuard, + WizardBevelsHidden, // Obsolete flags Uninstallable, @@ -167,6 +173,13 @@ struct header { std::string changes_associations; std::string architectures_allowed_expr; std::string architectures_installed_in_64bit_mode_expr; + std::string close_applications_filter_excludes; + std::string seven_zip_library_name; + std::string use_previous_app_dir; + std::string use_previous_group; + std::string use_previous_setup_type; + std::string use_previous_tasks; + std::string use_previous_user_info; std::string license_text; std::string info_before; std::string info_after; @@ -182,6 +195,7 @@ struct header { size_t component_count; size_t task_count; size_t directory_count; + size_t issig_key_count; size_t file_count; size_t data_entry_count; size_t icon_count; @@ -199,12 +213,30 @@ struct header { Color back_color2; Color image_back_color; Color small_image_back_color; + Color image_back_color_dynamic_dark; + Color small_image_back_color_dynamic_dark; + Color wizard_back_color; + Color wizard_back_color_dynamic_dark; + boost::uint8_t wizard_image_opacity; + boost::uint8_t wizard_back_image_opacity; enum style { ClassicStyle, ModernStyle }; style wizard_style; + enum dark_style { + LightStyle, + DarkStyle, + DynamicStyle + }; + dark_style wizard_dark_style; + enum light_control_styling { + LightControlStyleAll, + LightControlStyleAllButButtons, + LightControlStyleOnlyRequired + }; + light_control_styling wizard_light_control_styling; boost::uint32_t wizard_resize_percent_x; boost::uint32_t wizard_resize_percent_y; @@ -298,6 +330,8 @@ NAMED_ENUM(setup::header::alpha_format) NAMED_ENUM(setup::header::install_verbosity) NAMED_ENUM(setup::header::log_mode) NAMED_ENUM(setup::header::style) +NAMED_ENUM(setup::header::dark_style) +NAMED_ENUM(setup::header::light_control_styling) NAMED_ENUM(setup::header::auto_bool) NAMED_ENUM(setup::header::privilege_level) NAMED_ENUM(setup::header::language_detection_method) diff --git a/src/setup/info.cpp b/src/setup/info.cpp index d6969240..30a3c986 100644 --- a/src/setup/info.cpp +++ b/src/setup/info.cpp @@ -27,6 +27,7 @@ #include #include "crypto/hasher.hpp" +#include "crypto/crc32.hpp" #include "crypto/pbkdf2.hpp" #include "crypto/sha256.hpp" #include "crypto/xchacha20.hpp" @@ -37,6 +38,7 @@ #include "setup/file.hpp" #include "setup/icon.hpp" #include "setup/ini.hpp" +#include "setup/issigkey.hpp" #include "setup/item.hpp" #include "setup/language.hpp" #include "setup/message.hpp" @@ -104,6 +106,7 @@ void load_wizard_and_decompressor(std::istream & is, const setup::version & vers info.wizard_images.clear(); info.wizard_images_small.clear(); + info.wizard_images_back.clear(); load_wizard_images(is, version, info.wizard_images, entries); @@ -111,6 +114,29 @@ void load_wizard_and_decompressor(std::istream & is, const setup::version & vers load_wizard_images(is, version, info.wizard_images_small, entries); } + if(version >= INNO_VERSION(6, 7, 0)) { + // Inno Setup 6.7.0 added a "back image" wizard image group (used + // for the new WizardBackImageFile directive), in addition to the + // existing main and small image groups. See the wizard-image read + // block in Projects/Src/Setup.MainFunc.pas at tag is-6_7_0 in + // https://github.com/jrsoftware/issrc. + load_wizard_images(is, version, info.wizard_images_back, entries); + } + + if(version >= INNO_VERSION(6, 6, 0)) { + // Inno Setup 6.6.0 added a second copy of each wizard image + // group for the dynamic-dark theme. The on-disk format always + // includes both copies regardless of whether dark-mode images + // were actually configured; the runtime decides which set to + // use based on WantWizardImagesDynamicDark. + std::vector dark_images, dark_images_small, dark_images_back; + load_wizard_images(is, version, dark_images, entries); + load_wizard_images(is, version, dark_images_small, entries); + if(version >= INNO_VERSION(6, 7, 0)) { + load_wizard_images(is, version, dark_images_back, entries); + } + } + info.decompressor_dll.clear(); if(header.compression == stream::BZip2 || (header.compression == stream::LZMA1 && version == INNO_VERSION(4, 1, 5)) @@ -123,6 +149,16 @@ void load_wizard_and_decompressor(std::istream & is, const setup::version & vers } } + if(version >= INNO_VERSION(6, 5, 0) && !header.seven_zip_library_name.empty()) { + // Inno Setup 6.5.0 added an embedded 7-Zip decoder DLL stream + // after the decompressor DLL block, present whenever the [Setup] + // directive SevenZipLibraryName is set (which Setup uses to + // implement Extract7ZipArchive support added in 6.4.0). Skip it; + // innoextract does not need to decode 7-Zip archives during a + // listing. + util::binary_string::skip(is); + } + info.decrypt_dll.clear(); if((header.options & header::EncryptionUsed) && version < INNO_VERSION(6, 4, 0)) { if(entries & (info::DecryptDll | info::NoSkip)) { @@ -204,6 +240,8 @@ void info::try_load(std::istream & is, entry_types entries, util::codepage_id fo load_entries(*reader, entries, header.task_count, tasks, Tasks); debug("loading directories"); load_entries(*reader, entries, header.directory_count, directories, Directories); + debug("loading issig keys"); + load_entries(*reader, entries, header.issig_key_count, issig_keys, ISSigKeys); debug("loading files"); load_entries(*reader, entries, header.file_count, files, Files); debug("loading icons"); @@ -263,6 +301,41 @@ void info::load(std::istream & is, entry_types entries, util::codepage_id force_ entries |= NoSkip; } + if(version >= INNO_VERSION(6, 5, 0)) { + // Inno Setup 6.5.0 split the encryption metadata out of + // TSetupHeader into a standalone TSetupEncryptionHeader stored on + // the outer stream between the version magic and the first block + // (Projects/Src/Shared.Struct.pas at tag is-6_5_0 in + // https://github.com/jrsoftware/issrc). Read it here so the block + // reader starts at the right offset; innoextract does not yet + // support actually decrypting 6.5.0+ encrypted installers. + boost::uint32_t expected_crc = util::load(is); + crypto::crc32 checksum; + checksum.init(); + boost::uint8_t encryption_use = checksum.load(is); + if(encryption_use != 0) { + log_warning << "Encrypted setup not supported; cannot decrypt files"; + } + // 16-byte KDFSalt + 4-byte KDFIterations + // + 8-byte BaseNonce.RandomXorStartOffset + // + 4-byte BaseNonce.RandomXorFirstSlice + // + 12-byte BaseNonce.RemainingRandom (3 x 4 bytes) + // + 4-byte PasswordTest = 48 bytes after encryption_use. + for(int i = 0; i < 16; i++) { + (void)checksum.load(is); + } + (void)checksum.load(is); + (void)checksum.load(is); + (void)checksum.load(is); + for(int i = 0; i < 3; i++) { + (void)checksum.load(is); + } + (void)checksum.load(is); + if(checksum.finalize() != expected_crc) { + log_warning << "Encryption header checksum mismatch!"; + } + } + bool parsed_without_errors = false; std::streampos start = is.tellg(); for(;;) { diff --git a/src/setup/info.hpp b/src/setup/info.hpp index e08cfd43..10f7f078 100644 --- a/src/setup/info.hpp +++ b/src/setup/info.hpp @@ -43,6 +43,7 @@ struct directory_entry; struct file_entry; struct icon_entry; struct ini_entry; +struct issig_key_entry; struct language_entry; struct message_entry; struct permission_entry; @@ -69,6 +70,7 @@ struct info { Files, Icons, IniEntries, + ISSigKeys, Languages, Messages, Permissions, @@ -98,6 +100,7 @@ struct info { std::vector files; //! \c Files std::vector icons; //! \c Icons std::vector ini_entries; //! \c IniEntries + std::vector issig_keys; //! \c ISSigKeys std::vector languages; //! \c Languages std::vector messages; //! \c Messages std::vector permissions; //! \c Permissions @@ -111,6 +114,7 @@ struct info { //! Loading enabled by \c WizardImages std::vector wizard_images; std::vector wizard_images_small; + std::vector wizard_images_back; //!< Inno Setup 6.7.0+ background images //! Contents of the helper DLL used to decompress setup data in some versions. //! Loading enabled by \c DecompressorDll diff --git a/src/setup/issigkey.cpp b/src/setup/issigkey.cpp new file mode 100644 index 00000000..7d3bfc09 --- /dev/null +++ b/src/setup/issigkey.cpp @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2011-2019 Daniel Scharrer + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the author(s) be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +#include "setup/issigkey.hpp" + +#include "util/load.hpp" + +namespace setup { + +void issig_key_entry::load(std::istream & is, const info & /* i */) { + + // TSetupISSigKeyEntry: PublicX, PublicY, RuntimeID + // (Projects/Src/Shared.Struct.pas at tag is-6_5_0 in + // https://github.com/jrsoftware/issrc). + is >> util::binary_string(public_x); + is >> util::binary_string(public_y); + is >> util::binary_string(runtime_id); + +} + +} // namespace setup diff --git a/src/setup/issigkey.hpp b/src/setup/issigkey.hpp new file mode 100644 index 00000000..e027d435 --- /dev/null +++ b/src/setup/issigkey.hpp @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2011-2019 Daniel Scharrer + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the author(s) be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +/*! + * \file + * + * Structures for ISSig public key entries stored in Inno Setup 6.5.0+ files. + */ +#ifndef INNOEXTRACT_SETUP_ISSIGKEY_HPP +#define INNOEXTRACT_SETUP_ISSIGKEY_HPP + +#include +#include + +namespace setup { + +struct info; + +struct issig_key_entry { + + // introduced in 6.5.0 + + // X coordinate of the Ed25519 public key, as raw bytes. + std::string public_x; + + // Y coordinate of the Ed25519 public key, as raw bytes. + std::string public_y; + + // Tool-supplied identifier (typically the ISSigTool key file name). + std::string runtime_id; + + void load(std::istream & is, const info & i); + +}; + +} // namespace setup + +#endif // INNOEXTRACT_SETUP_ISSIGKEY_HPP diff --git a/src/setup/language.cpp b/src/setup/language.cpp index 6b408136..62be8401 100644 --- a/src/setup/language.cpp +++ b/src/setup/language.cpp @@ -140,9 +140,17 @@ void language_entry::load(std::istream & is, const info & i) { } is >> util::binary_string(dialog_font); - is >> util::binary_string(title_font); + if(i.version < INNO_VERSION(6, 6, 0)) { + is >> util::binary_string(title_font); + } else { + title_font.clear(); + } is >> util::binary_string(welcome_font); - is >> util::binary_string(copyright_font); + if(i.version < INNO_VERSION(6, 6, 0)) { + is >> util::binary_string(copyright_font); + } else { + copyright_font.clear(); + } if(i.version >= INNO_VERSION(4, 0, 0)) { is >> util::binary_string(data); @@ -156,7 +164,15 @@ void language_entry::load(std::istream & is, const info & i) { license_text.clear(), info_before.clear(), info_after.clear(); } - language_id = util::load(is); + if(i.version >= INNO_VERSION(6, 6, 0)) { + // Inno Setup 6.6.0 narrowed LanguageID from Cardinal (4 bytes) to + // Word (2 bytes) on disk; see TSetupLanguageEntry in + // Projects/Src/Shared.Struct.pas at tag is-6_6_0 in + // https://github.com/jrsoftware/issrc. + language_id = util::load(is); + } else { + language_id = util::load(is); + } if(i.version < INNO_VERSION(4, 2, 2)) { codepage = default_codepage_for_language(language_id); @@ -186,9 +202,26 @@ void language_entry::load(std::istream & is, const info & i) { dialog_font_standard_height = 0; } - title_font_size = util::load(is); + if(i.version >= INNO_VERSION(6, 6, 0)) { + // 6.6.0 replaced TitleFontSize and CopyrightFontSize with + // DialogFontBaseScaleHeight and DialogFontBaseScaleWidth (each a + // 32-bit Integer) sitting between DialogFontSize and + // WelcomeFontSize. See TSetupLanguageEntry in + // Projects/Src/Shared.Struct.pas at tag is-6_6_0. + dialog_font_base_scale_height = util::load(is); + dialog_font_base_scale_width = util::load(is); + title_font_size = 0; + } else { + dialog_font_base_scale_height = 0; + dialog_font_base_scale_width = 0; + title_font_size = util::load(is); + } welcome_font_size = util::load(is); - copyright_font_size = util::load(is); + if(i.version < INNO_VERSION(6, 6, 0)) { + copyright_font_size = util::load(is); + } else { + copyright_font_size = 0; + } if(i.version == INNO_VERSION_EXT(5, 5, 7, 1)) { util::load(is); // always 8 or 9? diff --git a/src/setup/language.hpp b/src/setup/language.hpp index 8801c643..99b65d0e 100644 --- a/src/setup/language.hpp +++ b/src/setup/language.hpp @@ -56,6 +56,8 @@ struct language_entry { boost::uint32_t codepage; size_t dialog_font_size; size_t dialog_font_standard_height; + size_t dialog_font_base_scale_height; //!< Inno Setup 6.6.0+ + size_t dialog_font_base_scale_width; //!< Inno Setup 6.6.0+ size_t title_font_size; size_t welcome_font_size; size_t copyright_font_size; diff --git a/src/setup/task.cpp b/src/setup/task.cpp index a0bbdd8c..6fbe1ef8 100644 --- a/src/setup/task.cpp +++ b/src/setup/task.cpp @@ -45,7 +45,12 @@ void task_entry::load(std::istream & is, const info & i) { } else { check.clear(); } - if(i.version >= INNO_VERSION(4, 0, 0) || (i.version.is_isx() && i.version >= INNO_VERSION(3, 0, 3))) { + if(i.version >= INNO_VERSION(6, 7, 0)) { + // Inno Setup 6.7.0 narrowed TSetupTaskEntry.Level from Integer + // (4 bytes) to Byte (1 byte). See Projects/Src/Shared.Struct.pas + // at tag is-6_7_0 in https://github.com/jrsoftware/issrc. + level = util::load(is); + } else if(i.version >= INNO_VERSION(4, 0, 0) || (i.version.is_isx() && i.version >= INNO_VERSION(3, 0, 3))) { level = util::load(is); } else { level = 0; diff --git a/src/setup/version.cpp b/src/setup/version.cpp index 7f68af26..01582aa6 100644 --- a/src/setup/version.cpp +++ b/src/setup/version.cpp @@ -186,6 +186,13 @@ const known_version versions[] = { { "Inno Setup Setup Data (6.3.0)", INNO_VERSION_EXT(6, 3, 0, 0), version::Unicode }, { "Inno Setup Setup Data (6.4.0)", /* prerelease */ INNO_VERSION_EXT(6, 4, 0, 0), version::Unicode }, { "Inno Setup Setup Data (6.4.0.1)", /* 6.4.0 */ INNO_VERSION_EXT(6, 4, 0, 1), version::Unicode }, + { "Inno Setup Setup Data (6.4.2)", INNO_VERSION_EXT(6, 4, 2, 0), version::Unicode }, + { "Inno Setup Setup Data (6.4.3)", INNO_VERSION_EXT(6, 4, 3, 0), version::Unicode }, + { "Inno Setup Setup Data (6.5.0)", INNO_VERSION_EXT(6, 5, 0, 0), version::Unicode }, + { "Inno Setup Setup Data (6.5.2)", INNO_VERSION_EXT(6, 5, 2, 0), version::Unicode }, + { "Inno Setup Setup Data (6.6.0)", INNO_VERSION_EXT(6, 6, 0, 0), version::Unicode }, + { "Inno Setup Setup Data (6.6.1)", INNO_VERSION_EXT(6, 6, 1, 0), version::Unicode }, + { "Inno Setup Setup Data (6.7.0)", INNO_VERSION_EXT(6, 7, 0, 0), version::Unicode }, }; } // anonymous namespace diff --git a/src/stream/block.cpp b/src/stream/block.cpp index 2a90c209..9a7d958a 100644 --- a/src/stream/block.cpp +++ b/src/stream/block.cpp @@ -158,12 +158,21 @@ block_reader::pointer block_reader::get(std::istream & base, const setup::versio crypto::crc32 actual_checksum; actual_checksum.init(); - boost::uint32_t stored_size; + boost::uint64_t stored_size; block_compression compression; if(version >= INNO_VERSION(4, 0, 9)) { - stored_size = actual_checksum.load(base); + if(version >= INNO_VERSION(6, 7, 0)) { + // Inno Setup 6.7.0 widened the block-header stored_size field + // from 4 to 8 bytes; the on-disk struct is now Int64 instead of + // LongWord, allowing data blocks larger than 4 GiB. See + // Projects/Src/Stream.BlockReader.pas at tag is-6_7_0 in + // https://github.com/jrsoftware/issrc. + stored_size = actual_checksum.load(base); + } else { + stored_size = actual_checksum.load(base); + } boost::uint8_t compressed = actual_checksum.load(base); compression = compressed ? (version >= INNO_VERSION(4, 1, 6) ? LZMA1 : Zlib) : Stored; @@ -180,7 +189,7 @@ block_reader::pointer block_reader::get(std::istream & base, const setup::versio } // Add the size of a CRC32 checksum for each 4KiB subblock. - stored_size += boost::uint32_t(util::ceildiv(stored_size, 4096) * 4); + stored_size += util::ceildiv(stored_size, 4096) * 4; } if(actual_checksum.finalize() != expected_checksum) { @@ -204,7 +213,7 @@ block_reader::pointer block_reader::get(std::istream & base, const setup::versio fis->push(inno_block_filter(), 4096); - fis->push(io::restrict(base, 0, stored_size)); + fis->push(io::restrict(base, std::streamoff(0), std::streamoff(stored_size))); fis->exceptions(std::ios_base::badbit | std::ios_base::failbit); diff --git a/src/stream/chunk.hpp b/src/stream/chunk.hpp index fae34840..16c3ccb6 100644 --- a/src/stream/chunk.hpp +++ b/src/stream/chunk.hpp @@ -81,9 +81,9 @@ struct chunk { boost::uint32_t first_slice; //!< Slice where the chunk starts. boost::uint32_t last_slice; //!< Slice where the chunk ends. - boost::uint32_t sort_offset; + boost::uint64_t sort_offset; - boost::uint32_t offset; //!< Offset of the compressed chunk in firstSlice. + boost::uint64_t offset; //!< Offset of the compressed chunk in firstSlice. boost::uint64_t size; //! Total compressed size of the chunk. compression_method compression; //!< Compression method used by the chunk. diff --git a/src/stream/slice.cpp b/src/stream/slice.cpp index c638f405..6e28db04 100644 --- a/src/stream/slice.cpp +++ b/src/stream/slice.cpp @@ -50,7 +50,7 @@ const char slice_ids[][8] = { } // anonymous namespace -slice_reader::slice_reader(std::istream * istream, boost::uint32_t offset) +slice_reader::slice_reader(std::istream * istream, boost::uint64_t offset) : data_offset(offset), slices_per_disk(1), current_slice(0), slice_size(0), is(istream) { @@ -59,8 +59,8 @@ slice_reader::slice_reader(std::istream * istream, boost::uint32_t offset) std::streampos file_size = is->seekg(0, std::ios_base::end).tellg(); - slice_size = boost::uint32_t(std::min(file_size, max_size)); - if(is->seekg(data_offset).fail()) { + slice_size = boost::uint64_t(std::min(file_size, max_size)); + if(is->seekg(std::streamoff(data_offset)).fail()) { throw slice_error("could not seek to data"); } } @@ -125,12 +125,12 @@ bool slice_reader::open_file(const path_type & file) { if(ifs.fail()) { ifs.close(); throw slice_error("could not read slice size in \"" + file.string() + "\""); - } else if(std::streampos(slice_size) > file_size) { + } else if(std::streampos(std::streamoff(slice_size)) > file_size) { ifs.close(); std::ostringstream oss; oss << "bad slice size in " << file << ": " << slice_size << " > " << file_size; throw slice_error(oss.str()); - } else if(std::streampos(slice_size) < ifs.tellg()) { + } else if(std::streampos(std::streamoff(slice_size)) < ifs.tellg()) { ifs.close(); std::ostringstream oss; oss << "bad slice size in " << file << ": " << slice_size << " < " << ifs.tellg(); @@ -208,7 +208,7 @@ void slice_reader::open(size_t slice) { throw slice_error(oss.str()); } -bool slice_reader::seek(size_t slice, boost::uint32_t offset) { +bool slice_reader::seek(size_t slice, boost::uint64_t offset) { seek(slice); @@ -218,7 +218,7 @@ bool slice_reader::seek(size_t slice, boost::uint32_t offset) { return false; } - if(is->seekg(offset).fail()) { + if(is->seekg(std::streamoff(offset)).fail()) { return false; } @@ -233,21 +233,21 @@ std::streamsize slice_reader::read(char * buffer, std::streamsize bytes) { while(bytes > 0) { - boost::uint32_t read_pos = boost::uint32_t(is->tellg()); + boost::uint64_t read_pos = boost::uint64_t(is->tellg()); if(read_pos > slice_size) { break; } - boost::uint32_t remaining = slice_size - read_pos; + boost::uint64_t remaining = slice_size - read_pos; if(!remaining) { seek(current_slice + 1); - read_pos = boost::uint32_t(is->tellg()); + read_pos = boost::uint64_t(is->tellg()); if(read_pos > slice_size) { break; } remaining = slice_size - read_pos; } - boost::uint64_t toread = std::min(boost::uint64_t(remaining), boost::uint64_t(bytes)); + boost::uint64_t toread = std::min(remaining, boost::uint64_t(bytes)); toread = std::min(toread, boost::uint64_t(std::numeric_limits::max())); if(is->read(buffer, std::streamsize(toread)).fail()) { break; diff --git a/src/stream/slice.hpp b/src/stream/slice.hpp index b2ec52c0..d0b3bc02 100644 --- a/src/stream/slice.hpp +++ b/src/stream/slice.hpp @@ -59,7 +59,7 @@ class slice_reader : public boost::iostreams::source { typedef boost::filesystem::path path_type; // Information for reading embedded setup data - const boost::uint32_t data_offset; + const boost::uint64_t data_offset; // Information for eading external setup data path_type dir; //!< Slice directory specified at construction. @@ -69,7 +69,7 @@ class slice_reader : public boost::iostreams::source { // Information about the current slice size_t current_slice; //!< Number of the currently opened slice. - boost::uint32_t slice_size; //!< Size in bytes of the currently opened slice. + boost::uint64_t slice_size; //!< Size in bytes of the currently opened slice. // Streams util::ifstream ifs; //!< File input stream used when reading from external slices. @@ -97,7 +97,7 @@ class slice_reader : public boost::iostreams::source { * The constructed reader will allow reading the byte range [data_offset, file end) * from the setup executable and provide this as the range [0, file end - data_offset). */ - slice_reader(std::istream * istream, boost::uint32_t offset); + slice_reader(std::istream * istream, boost::uint64_t offset); /*! * Construct a \ref slice_reader to read from external data slices (aka disks). @@ -126,7 +126,7 @@ class slice_reader : public boost::iostreams::source { * \return \c false if the requested slice could not be opened, or if the requested * offset is not a valid position in that slice - \c true otherwise. */ - bool seek(size_t slice, boost::uint32_t offset); + bool seek(size_t slice, boost::uint64_t offset); /*! * Read a number of bytes starting at the current slice and offset within that slice. diff --git a/src/util/storedenum.hpp b/src/util/storedenum.hpp index 0ccf3338..00aec390 100644 --- a/src/util/storedenum.hpp +++ b/src/util/storedenum.hpp @@ -244,6 +244,22 @@ class stored_flag_reader { return result; } + //! Number of stored bytes already read from the stream. + size_t bytes_consumed() const { return bytes; } + + /*! + * Discard padding bytes so that the total number of bytes read from + * the stream is at least `target`. Used for setups (e.g. Inno Setup + * 6.7.0) that always pad the bitset to a fixed width regardless of + * the number of flags actually defined. + */ + void discard_padding_to(size_t target) { + while(bytes < target) { + (void)util::load(stream); + bytes++; + } + } + }; template