New Upstream Release - innoextract

Ready changes

Summary

Merged new upstream version: 1.9 (was: 1.8).

Resulting package

Built on 2022-03-14T06:48 (took 2m52s)

The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:

apt install -t fresh-releases innoextract-dbgsymapt install -t fresh-releases innoextract

Lintian Result

Diff

diff --git a/CHANGELOG b/CHANGELOG
index cfd1487..2598562 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,4 +1,14 @@
 
+innoextract 1.9 (2020-08-09)
+ - Added preliminary support for Inno Setup 6.1.0
+ - Added support for a modified Inno Setup 5.4.2 variant
+ - Fixed output directory being created for unsupported installers
+ - Fixed some safe non-ASCII characters being stripped from filenames
+ - Fixed handling of path separators in Japanese and Korean installers
+ - Fixed build with newer Boost versions
+ - Windows: Fixed heap corruption
+ - Windows: Fixed garbled output
+
 innoextract 1.8 (2019-09-15)
  - Added support for Inno Setup 6.0.0 installers
  - Added support for pre-release Inno Setup 5.6.2 installers used by GOG
diff --git a/CMakeLists.txt b/CMakeLists.txt
index b98e59d..dbb64f1 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,13 +1,11 @@
-project(innoextract)
-
-cmake_minimum_required(VERSION 2.8)
+cmake_minimum_required(VERSION 2.8...3.15)
 
-if(CMAKE_VERSION VERSION_GREATER 3.15)
-	cmake_policy(VERSION 3.15)
-else()
+if(CMAKE_VERSION VERSION_LESS 3.12)
 	cmake_policy(VERSION ${CMAKE_VERSION})
 endif()
 
+project(innoextract)
+
 
 # Define configuration options
 
@@ -135,21 +133,6 @@ if(USE_STATIC_LIBS AND NOT MSVC)
 	add_ldflag("-static-libgcc")
 endif()
 
-# Force re-checking libraries if the compiler or compiler flags change
-if((NOT LAST_CMAKE_CXX_FLAGS STREQUAL CMAKE_CXX_FLAGS)
-   OR (NOT LAST_CMAKE_CXX_COMPILER STREQUAL CMAKE_CXX_COMPILER))
-	force_recheck_library(LZMA)
-	force_recheck_library(Boost)
-	force_recheck_library(ZLIB)
-	force_recheck_library(BZip2)
-	force_recheck_library(iconv)
-	unset(Boost_INCLUDE_DIR CACHE)
-	set(LAST_CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}" CACHE INTERNAL
-	    "The last C++ compiler flags")
-	set(LAST_CMAKE_CXX_COMPILER "${CMAKE_CXX_COMPILER}" CACHE INTERNAL
-	    "The last C++ compiler")
-endif()
-
 unset(LIBRARIES)
 
 if(USE_ARC4)
@@ -158,7 +141,6 @@ endif()
 
 if(USE_LZMA)
 	find_package(LZMA REQUIRED)
-	check_link_library(LZMA LZMA_LIBRARIES)
 	list(APPEND LIBRARIES ${LZMA_LIBRARIES})
 	include_directories(SYSTEM ${LZMA_INCLUDE_DIR})
 	add_definitions(${LZMA_DEFINITIONS})
@@ -176,7 +158,6 @@ find_package(Boost REQUIRED COMPONENTS
 	system
 	program_options
 )
-check_link_library(Boost Boost_LIBRARIES)
 list(APPEND LIBRARIES ${Boost_LIBRARIES})
 link_directories(${Boost_LIBRARY_DIRS})
 include_directories(SYSTEM ${Boost_INCLUDE_DIR})
@@ -210,7 +191,6 @@ if(Boost_HAS_STATIC_LIBS)
 				break()
 			endif()
 		endforeach()
-		check_link_library(${Lib} ${LIB}_LIBRARIES)
 		list(APPEND LIBRARIES ${${LIB}_LIBRARIES})
 	endforeach()
 endif()
@@ -227,7 +207,6 @@ elseif(NOT WITH_CONV OR WITH_CONV STREQUAL "iconv")
 	endif()
 	find_package(iconv ${ICONV_REQUIRED})
 	if(ICONV_FOUND)
-		check_link_library(iconv iconv_LIBRARIES)
 		list(APPEND LIBRARIES ${iconv_LIBRARIES})
 		include_directories(SYSTEM ${iconv_INCLUDE_DIR})
 		add_definitions(${iconv_DEFINITIONS})
@@ -337,6 +316,11 @@ if(NOT MSVC)
 	
 endif()
 
+if($ENV{PORTAGE_REPO_NAME} MATCHES "gentoo")
+	# Meh
+	unset(LIBRARIES)
+endif()
+
 
 # All sources:
 
@@ -539,3 +523,12 @@ print_configuration("Charset conversion"
 	1                             "builtin"
 )
 message("")
+
+if(DEVELOPER)
+	file(READ "README.md" readme)
+	parse_version_file("VERSION" "VERSION")
+	string(REPLACE "${VERSION_2}" "" readme_without_version "${readme}")
+	if(readme_without_version STREQUAL readme)
+		message(WARNING "Could not find '${VERSION_2}' in README.md.")
+	endif()
+endif()
diff --git a/LICENSE b/LICENSE
index 0456873..446f0c7 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,5 +1,5 @@
 
-Copyright (C) 2011-2019 Daniel Scharrer <daniel@constexpr.org>
+Copyright (C) 2011-2020 Daniel Scharrer <daniel@constexpr.org>
 
 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
diff --git a/README.md b/README.md
index 35f6f1a..a5ae683 100644
--- a/README.md
+++ b/README.md
@@ -1,33 +1,33 @@
 
 # innoextract - A tool to unpack installers created by Inno Setup
 
-[Inno Setup](http://www.jrsoftware.org/isinfo.php) is a tool to create installers for Microsoft Windows applications. innoextract allows to extract such installers under non-Windows systems without running the actual installer using wine. innoextract currently supports installers created by Inno Setup 1.2.10 to 5.6.0.
+[Inno Setup](https://jrsoftware.org/isinfo.php) is a tool to create installers for Microsoft Windows applications. innoextract allows to extract such installers under non-Windows systems without running the actual installer using wine. innoextract currently supports installers created by Inno Setup 1.2.10 to 6.0.5.
 
 In addition to standard Inno Setup installers, innoextract also supports some modified Inno Setup variants including Martijn Laan's My Inno Setup Extensions 3.0.6.1 as well as GOG.com's Inno Setup-based game installers.
 
 innoextract is available under the ZLIB license - see the LICENSE file.
 
-See the website for [Linux packages](http://constexpr.org/innoextract/#packages).
+See the website for [Linux packages](https://constexpr.org/innoextract/#packages).
 
 ## Contact
 
-[Website](http://constexpr.org/innoextract/)
+[Website](https://constexpr.org/innoextract/)
 
-Author: [Daniel Scharrer](http://constexpr.org/)
+Author: [Daniel Scharrer](https://constexpr.org/)
 
 ## Dependencies
 
-* **[Boost](http://www.boost.org/) 1.37** or newer
-* **liblzma** from [xz-utils](http://tukaani.org/xz/) *(optional)*
-* **iconv** (*optional*, either as part of the system libc, as is the case with [glibc](http://www.gnu.org/software/libc/) and [uClibc](http://www.uclibc.org/), or as a separate [libiconv](http://www.gnu.org/software/libiconv/))
+* **[Boost](https://www.boost.org/) 1.37** or newer
+* **liblzma** from [xz-utils](https://tukaani.org/xz/) *(optional)*
+* **iconv** (*optional*, either as part of the system libc, as is the case with [glibc](https://www.gnu.org/software/libc/) and [uClibc](https://uclibc.org/), or as a separate [libiconv](https://www.gnu.org/software/libiconv/))
 
 For Boost you will need the headers as well as the `iostreams`, `filesystem`, `date_time`, `system` and `program_options` libraries. Older Boost version may work but are not actively supported. The boost `iostreams` library needs to be build with zlib and bzip2 support.
 
 While innoextract can be built without liblzma by manually setting `-DUSE_LZMA=OFF`, it is highly recommended and you won't be able to extract most installers created by newer Inno Setup versions without it.
 
-To build innoextract you will also need **[CMake](http://cmake.org/) 2.8** and a working C++ compiler, as well as the development headers for liblzma and boost.
+To build innoextract you will also need **[CMake](https://cmake.org/) 2.8** and a working C++ compiler, as well as the development headers for liblzma and boost.
 
-See the Website for [operating system-specific instructions](http://constexpr.org/innoextract/install).
+See the Website for [operating system-specific instructions](https://constexpr.org/innoextract/install).
 
 ## Compile and install
 
@@ -115,10 +115,10 @@ A perhaps more complete, but Windows-only, tool to extract Inno Setup files is [
 
 Extracting Windows installer executables created by programs other than Inno Setup is out of the scope of this project. Some of these can be unpacked by the following programs:
 
-* [cabextract](http://www.cabextract.org.uk/)
+* [cabextract](https://cabextract.org.uk/)
 
 * [unshield](https://github.com/twogood/unshield)
 
 ## Disclaimer
 
-This project is in no way associated with Inno Setup or [www.jrsoftware.org](http://www.jrsoftware.org/).
+This project is in no way associated with Inno Setup or [jrsoftware.org](https://jrsoftware.org/).
diff --git a/VERSION b/VERSION
index ed1159f..3a2f1c3 100644
--- a/VERSION
+++ b/VERSION
@@ -1,7 +1,7 @@
-innoextract 1.8
+innoextract 1.9
 
 Known working Inno Setup versions:
-Inno Setup 1.2.10 to 6.0.2
+Inno Setup 1.2.10 to 6.0.5
 
 Bug tracker:
-http://innoextract.constexpr.org/issues
+https://innoextract.constexpr.org/issues
diff --git a/cmake/CXXVersionCheck.cmake b/cmake/CXXVersionCheck.cmake
index fdc6a11..34ae935 100644
--- a/cmake/CXXVersionCheck.cmake
+++ b/cmake/CXXVersionCheck.cmake
@@ -1,5 +1,5 @@
 
-# Copyright (C) 2013-2019 Daniel Scharrer
+# Copyright (C) 2013-2020 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
@@ -21,7 +21,8 @@ include(CheckCXXSourceCompiles)
 include(CompileCheck)
 
 set(CXX_VERSION 2003)
-set(CXX_CHECK_DIR "${CMAKE_CURRENT_LIST_DIR}/check")
+get_filename_component(CXX_CHECK_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH)
+set(CXX_CHECK_DIR "${CXX_CHECK_DIR}/check")
 
 function(enable_cxx_version version)
 	
diff --git a/cmake/CompileCheck.cmake b/cmake/CompileCheck.cmake
index 1e88599..94b1ee7 100644
--- a/cmake/CompileCheck.cmake
+++ b/cmake/CompileCheck.cmake
@@ -1,5 +1,5 @@
 
-# Copyright (C) 2011-2019 Daniel Scharrer
+# Copyright (C) 2011-2020 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
@@ -131,7 +131,7 @@ function(check_flag RESULT FLAG TYPE)
 endfunction(check_flag)
 
 macro(strip_warning_flags VAR)
-	string(REGEX REPLACE "(^| )\\-(W[^ ]*|pedantic)" "" ${VAR} "${${VAR}}")
+	string(REGEX REPLACE "(^| )\\-(W[^ l][^ ]*|Wl[^,][^ ]*|pedantic)" "" ${VAR} "${${VAR}}")
 endmacro()
 
 function(check_builtin RESULT EXPR)
@@ -191,96 +191,3 @@ function(add_ldflag FLAG)
 	endif()
 	
 endfunction(add_ldflag)
-
-function(try_link_library LIBRARY_NAME LIBRARY_FILE ERROR_VAR)
-	# See if we can link a simple program with the library using the configured c++ compiler
-	set(link_test_file "${CMAKE_CURRENT_BINARY_DIR}/link_test.cpp")
-	file(WRITE ${link_test_file} "int main(){}\n")
-	if(CMAKE_THREAD_LIBS_INIT)
-		list(APPEND LIBRARY_FILE "${CMAKE_THREAD_LIBS_INIT}")
-	endif()
-	try_compile(
-		CHECK_${LIBRARY_NAME}_LINK "${PROJECT_BINARY_DIR}" "${link_test_file}"
-		CMAKE_FLAGS "-DLINK_LIBRARIES=${LIBRARY_FILE}"
-		            "-DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS}"
-		            "-DCMAKE_EXE_LINKER_FLAGS=${CMAKE_EXE_LINKER_FLAGS}"
-		            "-DCMAKE_SHARED_LINKER_FLAGS=${CMAKE_SHARED_LINKER_FLAGS}"
-		            "-DCMAKE_MODULE_LINKER_FLAGS=${CMAKE_MODULE_LINKER_FLAGS}"
-		OUTPUT_VARIABLE ERRORLOG
-	)
-	set(${ERROR_VAR} "${ERRORLOG}" PARENT_SCOPE)
-endfunction(try_link_library)
-
-##############################################################################
-# Check that a a library actually works for the current configuration
-# This is neede because CMake prefers /usr/lib over /usr/lib32 for -m32 builds
-# See https://public.kitware.com/Bug/view.php?id=11260
-function(check_link_library LIBRARY_NAME LIBRARY_VARIABLE)
-	
-	if(MSVC)
-		# The main point of this is to work around CMakes ignorance of lib32.
-		# This doesn't really apply for systems that don't use a unix-like library dir layout.
-		return()
-	endif()
-	
-	set(lib_current "${${LIBRARY_VARIABLE}}")
-	set(found_var "ARX_CLL_${LIBRARY_NAME}_FOUND")
-	set(working_var "ARX_CLL_${LIBRARY_NAME}_WORKING")
-	
-	if(CHECK_${LIBRARY_NAME}_LINK)
-		set(lib_found "${${found_var}}")
-		set(lib_working "${${working_var}}")
-		if((lib_current STREQUAL lib_found) OR (lib_current STREQUAL lib_working))
-			set("${LIBRARY_VARIABLE}" "${lib_working}" PARENT_SCOPE)
-			return()
-		endif()
-	endif()
-	
-	set("${found_var}" "${lib_current}" CACHE INTERNAL "...")
-	
-	if(NOT lib_current STREQUAL "")
-		message(STATUS "Checking ${LIBRARY_NAME}: ${lib_current}")
-	endif()
-	
-	# Check if we can link to the full path found by find_package
-	try_link_library(${LIBRARY_NAME} "${lib_current}" ERRORLOG1)
-	
-	if(CHECK_${LIBRARY_NAME}_LINK)
-		set("${working_var}" "${lib_current}" CACHE INTERNAL "...")
-		return()
-	endif()
-	
-	# Check if the linker is smarter than cmake and try to link with only the library name
-	string(REGEX REPLACE "(^|;)[^;]*/lib([^;/]*)\\.so" "\\1-l\\2"
-	       LIBRARY_FILE "${lib_current}")
-	
-	if(NOT LIBRARY_FILE STREQUAL lib_current)
-		
-		try_link_library(${LIBRARY_NAME} "${LIBRARY_FILE}" ERRORLOG2)
-		
-		if(CHECK_${LIBRARY_NAME}_LINK)
-			message(STATUS " -> using ${LIBRARY_FILE} instead")
-			set("${LIBRARY_VARIABLE}" "${LIBRARY_FILE}" PARENT_SCOPE)
-			set("${working_var}" "${LIBRARY_FILE}" CACHE INTERNAL "...")
-			return()
-		endif()
-		
-	endif()
-	
-	# Force cmake to search again, as the cached library doesn't work
-	unset(FIND_PACKAGE_MESSAGE_DETAILS_${ARGV2} CACHE)
-	unset(FIND_PACKAGE_MESSAGE_DETAILS_${LIBRARY_NAME} CACHE)
-	
-	message(FATAL_ERROR "\n${ERRORLOG1}\n\n${ERRORLOG2}\n\n"
-	        "!! No suitable version of ${LIBRARY_NAME} found.\n"
-	        "   Maybe you don't have the right (32 vs.64 bit) architecture installed?\n\n"
-	        "   Tried ${lib_current} and ${LIBRARY_FILE}\n"
-	        "   Using compiler ${CMAKE_CXX_COMPILER} ${CMAKE_CXX_FLAGS}\n\n\n")
-	
-endfunction(check_link_library)
-
-function(force_recheck_library LIBRARY_NAME)
-	unset(FIND_PACKAGE_MESSAGE_DETAILS_${ARGV1} CACHE)
-	unset(FIND_PACKAGE_MESSAGE_DETAILS_${LIBRARY_NAME} CACHE)
-	unset(CHECK_${LIBRARY_NAME}_LINK CACHE)
-endfunction()
diff --git a/cmake/VersionScript.cmake b/cmake/VersionScript.cmake
index 929bbbe..be04b36 100644
--- a/cmake/VersionScript.cmake
+++ b/cmake/VersionScript.cmake
@@ -1,5 +1,5 @@
 
-# Copyright (C) 2011-2016 Daniel Scharrer
+# Copyright (C) 2011-2020 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
@@ -26,102 +26,30 @@ if((NOT DEFINED INPUT) OR (NOT DEFINED OUTPUT) OR (NOT DEFINED VERSION_SOURCES)
 	message(SEND_ERROR "Invalid arguments.")
 endif()
 
+get_filename_component(VERSION_SCRIPT_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH)
+include("${VERSION_SCRIPT_DIR}/VersionString.cmake")
+
 # configure_file doesn't handle newlines correctly - pre-escape variables
-function(escape_var VAR)
+macro(_version_escape var string)
 	# Escape the escape character and quotes
-	string(REGEX REPLACE "([\\\\\"])" "\\\\\\1" escaped "${${VAR}}")
+	string(REGEX REPLACE "([\\\\\"])" "\\\\\\1" ${var} "${string}")
 	# Pull newlines out of string
-	string(REGEX REPLACE "\n" "\\\\n\"\n\t\"" escaped "${escaped}")
-	set(${VAR} "${escaped}" PARENT_SCOPE)
-endfunction(escape_var)
+	string(REGEX REPLACE "\n" "\\\\n\"\n\t\"" ${var} "${${var}}")
+endmacro()
 
 set(var "")
 foreach(arg IN LISTS VERSION_SOURCES)
-	
 	if(var STREQUAL "")
 		set(var ${arg})
 	else()
-		
-		file(READ "${arg}" ${var})
-		string(STRIP "${${var}}" ${var})
-		string(REGEX REPLACE "\r\n" "\n" ${var} "${${var}}")
-		string(REGEX REPLACE "\r" "\n" ${var} "${${var}}")
-		
-		# Split the version file into lines.
-		string(REGEX MATCHALL "[^\r\n]+" lines "${${var}}")
-		set(${var}_COUNT 0)
-		foreach(line IN LISTS lines)
-			
-			set(${var}_${${var}_COUNT} "${line}")
-			escape_var(${var}_${${var}_COUNT})
-			
-			# Find the first and last spaces
-			string(STRIP "${line}" line)
-			string(LENGTH "${line}" line_length)
-			set(first_space -1)
-			set(last_space ${line_length})
-			foreach(i RANGE ${line_length})
-				if(i LESS line_length)
-					string(SUBSTRING "${line}" ${i} 1 line_char)
-					if(line_char STREQUAL " ")
-						set(last_space ${i})
-						if(first_space EQUAL -1)
-							set(first_space ${i})
-						endif()
-					endif()
-				endif()
-			endforeach()
-			
-			if(first_space GREATER -1)
-				
-				# Get everything before the first space
-				string(SUBSTRING "${line}" 0 ${first_space} line_name)
-				string(STRIP "${line_name}" ${var}_${${var}_COUNT}_SHORTNAME)
-				escape_var(${var}_${${var}_COUNT}_SHORTNAME)
-				
-				# Get everything after the first space
-				math(EXPR num_length "${line_length} - ${first_space}")
-				string(SUBSTRING "${line}" ${first_space} ${num_length} line_num)
-				string(STRIP "${line_num}" ${var}_${${var}_COUNT}_STRING)
-				escape_var(${var}_${${var}_COUNT}_STRING)
-				
-			endif()
-			
-			if(line MATCHES " ([0-9]\\.[^ ]* \\+ )?[^ ]*$")
-				string(REGEX REPLACE " (([0-9]\\.[^ ]* \\+ )?[^ ]*)$" ""
-				       ${var}_${${var}_COUNT}_NAME "${line}")
-				string(LENGTH ${${var}_${${var}_COUNT}_NAME} begin)
-				math(EXPR begin "${begin} + 1")
-				math(EXPR length "${line_length} - ${begin}")
-				string(SUBSTRING "${line}" "${begin}" "${length}" ${var}_${${var}_COUNT}_NUMBER)
-				
-			else()
-				set(${var}_${${var}_COUNT}_NAME "${line}")
-				set(${var}_${${var}_COUNT}_NUMBER)
-			endif()
-			escape_var(${var}_${${var}_COUNT}_NAME)
-			escape_var(${var}_${${var}_COUNT}_NUMBER)
-			
-			math(EXPR ${var}_COUNT "${${var}_COUNT} + 1")
-		endforeach()
-		
-		string(REGEX REPLACE "\n\n.*$" "" ${var}_HEAD "${${var}}")
-		string(STRIP "${${var}_HEAD}" ${var}_HEAD)
-		string(REGEX MATCH "\n\n.*" ${var}_TAIL "${${var}}")
-		string(STRIP "${${var}_TAIL}" ${var}_TAIL)
-		
-		escape_var(${var})
-		escape_var(${var}_HEAD)
-		escape_var(${var}_TAIL)
-		
+		parse_version_file(${var} "${arg}" ON)
 		set(var "")
 	endif()
-	
 endforeach()
 
 # Check for a git directory and fill in the git commit hash if one exists.
 unset(GIT_COMMIT)
-if(EXISTS "${GIT_DIR}")
+if(NOT GIT_DIR STREQUAL "")
 	
 	unset(git_head)
 	
diff --git a/cmake/VersionString.cmake b/cmake/VersionString.cmake
index 40cd1ca..61918f5 100644
--- a/cmake/VersionString.cmake
+++ b/cmake/VersionString.cmake
@@ -1,5 +1,5 @@
 
-# Copyright (C) 2011-2016 Daniel Scharrer
+# Copyright (C) 2011-2020 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
@@ -17,8 +17,8 @@
 #    misrepresented as being the original software.
 # 3. This notice may not be removed or altered from any source distribution.
 
-get_filename_component(VERSION_STRING_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH)
-set(VERSION_STRING_SCRIPT "${VERSION_STRING_DIR}/VersionScript.cmake")
+get_filename_component(VERSION_SCRIPT_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH)
+set(VERSION_STRING_SCRIPT "${VERSION_SCRIPT_DIR}/VersionScript.cmake")
 
 # Create a rule to generate a version string at compile time.
 #
@@ -33,11 +33,20 @@ set(VERSION_STRING_SCRIPT "${VERSION_STRING_DIR}/VersionScript.cmake")
 # for each variable ${var}
 # - ${var}: The contents of the associated file
 # - ${var}_COUNT: Number of lines in the associated file
-# - ${var}_${i}: The ${i}-th line of the associated file
-# - ${var}_${i}_SHORTNAME: The first component of the ${i}-th line of the associated file
-# - ${var}_${i}_STRING: Everything except the first component of the ${i}-th line of the associated file
-# - ${var}_${i}_NAME: Everything except the last component of the ${i}-th line of the associated file
-# - ${var}_${i}_NUMBER: The last component of the ${i}-th line of the associated file
+# - For each line ${i}:
+#   - ${var}_${i}: The ${i}-th line of the associated file
+#    - ${var}_${i}_PREFIX: The first component in the line
+#    - ${var}_${i}_LINE: Everything except the first component of the line
+#    - ${var}_${i}_NAME: Everything except the last component of the line
+#    - ${var}_${i}_STRING: The last component (excluding optional suffix) of the line
+#    - ${var}_${i}_SUFFIX: Suffix (seperated by " + ") of the line
+#    - ${var}_${i}_MAJOR: First version component in ${var}_${i}_STRING
+#    - ${var}_${i}_MINOR: Second version component in ${var}_${i}_STRING
+#    - ${var}_${i}_PATCH: Third version component in ${var}_${i}_STRING
+#    - ${var}_${i}_BUILD: Fourth version component in ${var}_${i}_STRING
+#    - ${var}_${i}_NUMBER: Reassembled verion components
+#    - ${var}_${i}_PRERELEASE: If the version indicates a prerelease build
+#    - ${var}_${i}_PRIVATE: If the version indicates a private build
 # - ${var}_HEAD: The first paragraph of the associated file
 # - ${var}_TAIL: The remaining paragraphs of the associated file
 #
@@ -92,6 +101,8 @@ function(version_file SRC DST VERSION_SOURCES GIT_DIR)
 		if(EXISTS "${abs_git_dir}/logs/HEAD")
 			list(APPEND dependencies "${abs_git_dir}/logs/HEAD")
 		endif()
+	else()
+		set(abs_git_dir "")
 	endif()
 	
 	add_custom_command(
@@ -106,6 +117,8 @@ function(version_file SRC DST VERSION_SOURCES GIT_DIR)
 			"-DGIT_COMMAND=${GIT_COMMAND}"
 			${defines}
 			-P "${VERSION_STRING_SCRIPT}"
+		COMMAND
+			${CMAKE_COMMAND} -E touch "${abs_dst}"
 		MAIN_DEPENDENCY
 			"${abs_src}"
 		DEPENDS
@@ -114,4 +127,198 @@ function(version_file SRC DST VERSION_SOURCES GIT_DIR)
 		VERBATIM
 	)
 	
-endfunction(version_file)
+endfunction()
+
+macro(_version_escape var string)
+	set(${var} "${string}")
+endmacro()
+
+macro(_define_version_var_nostrip suffix contents)
+	_version_escape(${var}_${suffix} "${${contents}}")
+	list(APPEND variables ${var}_${suffix})
+endmacro()
+
+macro(_define_version_var suffix contents)
+	string(STRIP "${${contents}}" tmp)
+	_define_version_var_nostrip(${suffix} tmp)
+endmacro()
+
+macro(_define_version_line_var_nostrip suffix contents)
+	_define_version_var_nostrip(${i}_${suffix} ${contents})
+	if(line_name)
+		set(${line_name}_${suffix} "${${var}_${i}_${suffix}}")
+		list(APPEND variables ${line_name}_${suffix})
+	endif()
+endmacro()
+
+macro(_define_version_line_var suffix contents)
+	string(STRIP "${${contents}}" tmp)
+	_define_version_line_var_nostrip(${suffix} tmp)
+endmacro()
+
+function(parse_version_file names file)
+	
+	list(GET names 0 var)
+	
+	list(LENGTH names names_count)
+	
+	set(variables)
+	
+	file(READ "${file}" contents)
+	string(STRIP "${contents}" contents)
+	string(REGEX REPLACE "\r\n" "\n" contents "${contents}")
+	string(REGEX REPLACE "\r" "\n" contents "${contents}")
+	_version_escape(${var} "${contents}")
+	list(APPEND variables ${var})
+	
+	# Split the version file into lines.
+	string(REGEX MATCHALL "[^\r\n]+" lines "${contents}")
+	set(i 0)
+	foreach(line IN LISTS lines)
+		
+		if(i LESS names_count)
+			list(GET names ${i} line_name)
+		else()
+			set(line_name)
+		endif()
+		
+		_define_version_var(${i} line)
+		
+		if(line MATCHES "^([^ ]*) (.*)$")
+			set(prefix "${CMAKE_MATCH_1}")
+			set(notprefix "${CMAKE_MATCH_2}")
+			_define_version_line_var(PREFIX prefix)
+			_define_version_line_var(LINE notprefix)
+		else()
+			_define_version_line_var(PREFIX line)
+		endif()
+		
+		if(line MATCHES "^(.*[^+] )?([^ ]+)( \\+ [^ ]+)?$")
+			
+			set(name "${CMAKE_MATCH_1}")
+			set(string "${CMAKE_MATCH_2}")
+			set(suffix "${CMAKE_MATCH_3}")
+			
+			_define_version_line_var(NAME name)
+			_define_version_line_var(STRING string)
+			_define_version_line_var_nostrip(SUFFIX suffix)
+			
+			if(i GREATER 0 AND line_name)
+				set(${line_name} "${${var}_${i}_STRING}")
+				list(APPEND variables ${line_name})
+			endif()
+			
+			if(string MATCHES "^([0-9]+)(\\.([0-9]+)(\\.([0-9]+)(\\.([0-9]+))?)?)?(.*)?$")
+				
+				set(major "${CMAKE_MATCH_1}")
+				set(minor "${CMAKE_MATCH_3}")
+				set(patch "${CMAKE_MATCH_5}")
+				set(build "${CMAKE_MATCH_7}")
+				set(release "${CMAKE_MATCH_8}")
+				
+				set(error 0)
+				set(newpatch)
+				set(newbuild)
+				set(prerelease 0)
+				set(private 0)
+				if(release MATCHES "^\\-dev\\-([2-9][0-9][0-9][0-9]+)\\-([0-9][0-9])\\-([0-9][0-9])$")
+					set(prerelease 1)
+					set(newpatch "${CMAKE_MATCH_1}")
+					set(newbuild "${CMAKE_MATCH_2}${CMAKE_MATCH_3}")
+				elseif(release MATCHES "^\\-rc([0-9]+)$")
+					set(prerelease 1)
+					set(newpatch "9999")
+					set(newbuild "${CMAKE_MATCH_1}")
+				elseif(release MATCHES "^\\-r([0-9]+)$")
+					if(build STREQUAL "")
+						set(build "${CMAKE_MATCH_1}")
+					else()
+						set(error 1)
+					endif()
+				elseif(release OR suffix)
+					set(prerelease 1)
+					set(private 1)
+					set(newpatch 9999)
+					set(newbuild 9999)
+				endif()
+				
+				foreach(component IN ITEMS major minor patch build newpatch newbuild)
+					string(REGEX REPLACE "^0+" "" ${component} "${${component}}")
+					if(NOT ${component})
+						set(${component} 0)
+					endif()
+				endforeach()
+				
+				if(newpatch)
+					set(prerelease 1)
+					if(build)
+						set(error 1)
+					else()
+						if(patch)
+							if(newpatch EQUAL 9999)
+								math(EXPR patch "${patch} - 1")
+								if(newbuild EQUAL 9999)
+									set(build 9999)
+								else()
+									math(EXPR build "${newbuild} + 1000")
+								endif()
+							else()
+								set(error 1)
+							endif()
+						else()
+							if(minor)
+								math(EXPR minor "${minor} - 1")
+							else()
+								if(major)
+									math(EXPR major "${major} - 1")
+								else()
+									set(error 1)
+								endif()
+								set(minor 9999)
+							endif()
+							set(patch "${newpatch}")
+							set(build "${newbuild}")
+						endif()
+					endif()
+				endif()
+				
+				set(number "${major}")
+				if(minor OR patch OR build)
+					set(number "${number}.${minor}")
+					if(patch OR build)
+						set(number "${number}.${patch}")
+						if(build)
+							set(number "${number}.${build}")
+						endif()
+					endif()
+				endif()
+				
+				_define_version_line_var(MAJOR major)
+				_define_version_line_var(MINOR minor)
+				_define_version_line_var(PATCH patch)
+				_define_version_line_var(BUILD build)
+				_define_version_line_var(PRERELEASE prerelease)
+				_define_version_line_var(PRIVATE private)
+				_define_version_line_var(NUMBER number)
+				_define_version_line_var(ERROR error)
+				
+			endif()
+			
+		endif()
+		
+		math(EXPR i "${i} + 1")
+	endforeach()
+	
+	set(${var}_COUNT ${i} PARENT_SCOPE)
+	
+	string(REGEX REPLACE "\n\n.*$" "" head "${contents}")
+	_define_version_var(HEAD head)
+	
+	string(REGEX MATCH "\n\n.*" tail "${contents}")
+	_define_version_var(TAIL tail)
+	
+	foreach(var IN LISTS variables)
+		set(${var} "${${var}}" PARENT_SCOPE)
+	endforeach()
+	
+endfunction()
diff --git a/debian/changelog b/debian/changelog
index cce038b..6e4fb7c 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+innoextract (1.9-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Mon, 14 Mar 2022 06:45:36 -0000
+
 innoextract (1.8-1) unstable; urgency=medium
 
   * Move to new upstream version. Deprecating the last patch
diff --git a/doc/innoextract.1.in b/doc/innoextract.1.in
index d57a405..98cc71c 100644
--- a/doc/innoextract.1.in
+++ b/doc/innoextract.1.in
@@ -1,6 +1,6 @@
 .\" Manpage for innoextract.
 .\" Contact daniel@constexpr.org to correct errors or typos.
-.TH innoextract 1 "@CHANGELOG_0_NUMBER@" "@VERSION_0_NUMBER@@GIT_SUFFIX_7@"
+.TH innoextract 1 "@CHANGELOG_0_STRING@" "@VERSION_0_STRING@@VERSION_SUFFIX@@GIT_SUFFIX_7@"
 .SH NAME
 innoextract - tool to extract installers created by Inno Setup
 .SH SYNOPSIS
@@ -167,7 +167,7 @@ Try to process additional .bin files that have the same basename as the setup bu
 
 Extracting these RAR archives requires rar, unrar or lsar/unar command-line utilities to be in the PATH.
 
-The \fB\-\-list\fP, \fB\-\-test\fP, \fB\-\-extract\fP and \fB\-\-output\-dir\fP options are passed along to unrar/unar, but other options may be ignored for the RAR files. For multi-part RAR archives, the \fB\-\-test\fP requires a writable output directory for temporary files.
+The \fB\-\-list\fP, \fB\-\-test\fP, \fB\-\-extract\fP and \fB\-\-output\-dir\fP options are passed along to unrar/unar, but other options may be ignored for the RAR files. For multi-part RAR archives, the \fB\-\-test\fP requires a writable output directory for temporary files which can be specified using the \fB\-\-output\-dir\fP option. If the output directory does not exist it will be created and then removed after testing is done. Parent directories are not created. Temporary files created inside the directory are always removed.
 
 Note that is option is geared towards GOG.com installers. Other installers may come be bundled with different extraneous \fI.bin\fP which this option might not be able to handle.
 
@@ -358,12 +358,12 @@ Names for data slice/disk files in multi-file installers must follow the standar
 \fBcabextract\fP(1), \fBunar\fP(1), \fBunrar\fP(1), \fBunshield\fP(1), \fBtzset\fP(3)
 .SH BUGS
 .PP
-Please report bugs to http://innoextract.constexpr.org/issues.
+Please report bugs to https://innoextract.constexpr.org/issues.
 .SH CREDITS
 .PP
 \fBinnoextract\fP is distributed under the zlib/libpng license.  See the LICENSE file for details.
 .PP
-A website is available at http://constexpr.org/innoextract/.
+A website is available at https://constexpr.org/innoextract/.
 .PP
 This program uses the excellent lzma/xz decompression library written by Lasse Collin.
 .SH AUTHOR
diff --git a/src/cli/debug.cpp b/src/cli/debug.cpp
index 6c439eb..894b505 100644
--- a/src/cli/debug.cpp
+++ b/src/cli/debug.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2011-2019 Daniel Scharrer
+ * Copyright (C) 2011-2020 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
@@ -24,9 +24,11 @@
 #include <iostream>
 
 #include <boost/foreach.hpp>
+#include <boost/filesystem/operations.hpp>
 #include <boost/range/size.hpp>
 
 #include "loader/offsets.hpp"
+
 #include "setup/component.hpp"
 #include "setup/data.hpp"
 #include "setup/delete.hpp"
@@ -45,12 +47,17 @@
 #include "setup/task.hpp"
 #include "setup/type.hpp"
 #include "setup/version.hpp"
+
 #include "stream/block.hpp"
+
+#include "util/fstream.hpp"
 #include "util/load.hpp"
 #include "util/log.hpp"
 #include "util/output.hpp"
 #include "util/time.hpp"
 
+namespace fs = boost::filesystem;
+
 void print_offsets(const loader::offsets & offsets) {
 	
 	std::cout << "loaded offsets:" << '\n';
@@ -599,3 +606,54 @@ void print_info(const setup::info & info) {
 	
 	std::cout.setf(old, std::ios_base::boolalpha);
 }
+
+static void dump_headers(std::istream & is, const setup::version & version, const extract_options & o, int i) {
+	
+	std::string filename;
+	{
+		std::ostringstream oss;
+		oss << "headers" << i << ".bin";
+		filename = oss.str();
+	}
+	
+	const char * type = (i == 0 ? "primary" : "secondary");
+	if(!o.quiet) {
+		std::cout << "Dumping " << type << " setup headers to \""
+		          << color::white << filename << color::reset << "\"\n";
+	} else if(!o.silent) {
+		std::cout << filename << '\n';
+	}
+	
+	fs::path path = o.output_dir / filename;
+	util::ofstream ofs;
+	try {
+		ofs.open(path, std::ios_base::out | std::ios_base::trunc | std::ios_base::binary);
+		if(!ofs.is_open()) {
+			throw std::exception();
+		}
+	} catch(...) {
+		throw std::runtime_error("Could not open output file \"" + path.string() + '"');
+	}
+	
+	try {
+		ofs << stream::block_reader::get(is, version)->rdbuf();
+	} catch(const std::exception & e) {
+		std::ostringstream oss;
+		oss << "Stream error while dumping " << type << " setup headers!\n";
+		oss << " ├─ detected setup version: " << version << '\n';
+		oss << " └─ error reason: " << e.what();
+		throw format_error(oss.str());
+	}
+	
+}
+
+void dump_headers(std::istream & is, const loader::offsets & offsets, const extract_options & o) {
+	
+	setup::version version;
+	is.seekg(offsets.header_offset);
+	version.load(is);
+	
+	dump_headers(is, version, o, 0);
+	dump_headers(is, version, o, 1);
+	
+}
diff --git a/src/cli/debug.hpp b/src/cli/debug.hpp
index 97dfdd5..04a74dd 100644
--- a/src/cli/debug.hpp
+++ b/src/cli/debug.hpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2011-2014 Daniel Scharrer
+ * Copyright (C) 2011-2020 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
@@ -30,6 +30,8 @@
 
 #include "configure.hpp"
 
+#include "cli/extract.hpp"
+
 #ifdef DEBUG
 
 namespace loader { struct offsets; }
@@ -38,6 +40,8 @@ namespace setup { struct info; }
 void print_offsets(const loader::offsets & offsets);
 void print_info(const setup::info & info);
 
+void dump_headers(std::istream & is, const loader::offsets & offsets, const extract_options & o);
+
 #endif // DEBUG
 
 #endif // INNOEXTRACT_CLI_DEBUG_HPP
diff --git a/src/cli/extract.cpp b/src/cli/extract.cpp
index b92b491..91c3152 100644
--- a/src/cli/extract.cpp
+++ b/src/cli/extract.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2011-2019 Daniel Scharrer
+ * Copyright (C) 2011-2020 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
@@ -876,6 +876,18 @@ processed_entries filter_entries(const extract_options & o, const setup::info &
 	return processed;
 }
 
+void create_output_directory(const extract_options & o) {
+	
+	try {
+		if(!o.output_dir.empty() && !fs::exists(o.output_dir)) {
+			fs::create_directory(o.output_dir);
+		}
+	} catch(...) {
+		throw std::runtime_error("Could not create output directory \"" + o.output_dir.string() + '"');
+	}
+	
+}
+
 } // anonymous namespace
 
 void process_file(const fs::path & installer, const extract_options & o) {
@@ -904,6 +916,13 @@ void process_file(const fs::path & installer, const extract_options & o) {
 	loader::offsets offsets;
 	offsets.load(ifs);
 	
+	#ifdef DEBUG
+	if(logger::debug) {
+		print_offsets(offsets);
+		std::cout << '\n';
+	}
+	#endif
+	
 	if(o.data_version)  {
 		setup::version version;
 		ifs.seekg(offsets.header_offset);
@@ -916,12 +935,13 @@ void process_file(const fs::path & installer, const extract_options & o) {
 		return;
 	}
 	
-#ifdef DEBUG
-	if(logger::debug) {
-		print_offsets(offsets);
-		std::cout << '\n';
+	#ifdef DEBUG
+	if(o.dump_headers)  {
+		create_output_directory(o);
+		dump_headers(ifs, offsets, o);
+		return;
 	}
-#endif
+	#endif
 	
 	setup::info::entry_types entries = 0;
 	if(o.list || o.test || o.extract || (o.gog_galaxy && o.list_languages)) {
@@ -956,6 +976,13 @@ void process_file(const fs::path & installer, const extract_options & o) {
 			process_file(headerfile, o);
 			return;
 		}
+		if(offsets.found_magic) {
+			if(offsets.header_offset == 0) {
+				throw format_error("Could not determine location of setup headers!");
+			} else {
+				throw format_error("Could not determine setup data version!");
+			}
+		}
 		throw;
 	} catch(const std::exception & e) {
 		std::ostringstream oss;
@@ -1008,8 +1035,8 @@ void process_file(const fs::path & installer, const extract_options & o) {
 	
 	processed_entries processed = filter_entries(o, info);
 	
-	if(o.extract && !o.output_dir.empty()) {
-		fs::create_directories(o.output_dir);
+	if(o.extract) {
+		create_output_directory(o);
 	}
 	
 	if(o.list || o.extract) {
diff --git a/src/cli/extract.hpp b/src/cli/extract.hpp
index 7240e48..e6a84d6 100644
--- a/src/cli/extract.hpp
+++ b/src/cli/extract.hpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2014-2019 Daniel Scharrer
+ * Copyright (C) 2014-2020 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
@@ -57,6 +57,9 @@ struct extract_options {
 	bool list_checksums; //!< Show checksum information for files
 	
 	bool data_version; //!< Print the data version
+	#ifdef DEBUG
+	bool dump_headers; //!< Dump setup headers
+	#endif
 	bool list; //!< List files
 	bool test; //!< Test files (but don't extract)
 	bool extract; //!< Extract files
diff --git a/src/cli/gog.cpp b/src/cli/gog.cpp
index 29dc2e7..49da5c3 100644
--- a/src/cli/gog.cpp
+++ b/src/cli/gog.cpp
@@ -253,12 +253,17 @@ char hex_char(int c) {
 
 class temporary_directory : private boost::noncopyable {
 	
+	fs::path parent;
 	fs::path path;
 	
 public:
 	
 	explicit temporary_directory(const fs::path & base) {
 		try {
+			if(!base.empty() && !fs::exists(base)) {
+				fs::create_directory(base);
+				parent = base;
+			}
 			size_t tmpnum = 0;
 			std::ostringstream oss;
 			do {
@@ -266,7 +271,7 @@ public:
 				oss << "innoextract-tmp-" << tmpnum++;
 				path = base / oss.str();
 			} while(fs::exists(path));
-			fs::create_directories(path);
+			fs::create_directory(path);
 		} catch(...) {
 			path = fs::path();
 			throw std::runtime_error("Could not create temporary directory!");
@@ -277,6 +282,9 @@ public:
 		if(!path.empty()) {
 			try {
 				fs::remove_all(path);
+				if(!parent.empty()) {
+					fs::remove(parent);
+				}
 			} catch(...) {
 				log_error << "Could not remove temporary directory " << path << '!';
 			}
diff --git a/src/cli/main.cpp b/src/cli/main.cpp
index f103f0b..82b72d3 100644
--- a/src/cli/main.cpp
+++ b/src/cli/main.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2011-2019 Daniel Scharrer
+ * Copyright (C) 2011-2020 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
@@ -141,6 +141,9 @@ int main(int argc, char * argv[]) {
 		("show-password", "Show password check information")
 		("check-password", "Abort if the password is incorrect")
 		("data-version,V", "Only print the data version")
+		#ifdef DEBUG
+		("dump-headers", "Dump decompressed setup headers")
+		#endif
 	;
 	
 	po::options_description modifiers("Modifiers");
@@ -175,7 +178,7 @@ int main(int argc, char * argv[]) {
 		("color,c", po::value<bool>()->implicit_value(true), "Enable/disable color output")
 		("progress,p", po::value<bool>()->implicit_value(true), "Enable/disable the progress bar")
 		#ifdef DEBUG
-			("debug", "Output debug information")
+		("debug", "Output debug information")
 		#endif
 	;
 	
@@ -374,14 +377,6 @@ int main(int argc, char * argv[]) {
 			 * See https://svn.boost.org/trac/boost/ticket/8535
 			 */
 			o.output_dir = i->second.as<std::string>();
-			try {
-				if(o.extract && !o.output_dir.empty() && !fs::exists(o.output_dir)) {
-					fs::create_directory(o.output_dir);
-				}
-			} catch(...) {
-				log_error << "Could not create output directory " << o.output_dir;
-				return ExitDataError;
-			}
 		}
 	}
 	
@@ -437,6 +432,16 @@ int main(int argc, char * argv[]) {
 		}
 	}
 	
+	#ifdef DEBUG
+	o.dump_headers = (options.count("dump-headers") != 0);
+	if(o.dump_headers) {
+		if(explicit_action || o.data_version) {
+			log_error << "Combining --dump-headers with other options is not allowed";
+			return ExitUserError;
+		}
+	}
+	#endif
+	
 	o.extract_unknown = (options.count("no-extract-unknown") == 0);
 	
 	const std::vector<std::string> & files = options["setup-files"]
diff --git a/src/loader/offsets.cpp b/src/loader/offsets.cpp
index e573924..0c0d6a4 100644
--- a/src/loader/offsets.cpp
+++ b/src/loader/offsets.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2011-2019 Daniel Scharrer
+ * Copyright (C) 2011-2020 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
@@ -62,7 +62,7 @@ const setup_loader_version known_setup_loader_versions[] = {
 const int ResourceNameInstaller = 11111;
 
 const boost::uint32_t SetupLoaderHeaderOffset = 0x30;
-const boost::uint32_t SetupLoaderHeaderMagic = 0x6f6e6e49;
+const boost::uint32_t SetupLoaderHeaderMagic = 0x6f6e6e49; // "Inno"
 
 } // anonymous namespace
 
@@ -76,13 +76,21 @@ bool offsets::load_from_exe_file(std::istream & is) {
 		return false;
 	}
 	
+	debug("found Inno magic at " << print_hex(SetupLoaderHeaderOffset));
+	
+	found_magic = true;
+	
 	boost::uint32_t offset_table_offset = util::load<boost::uint32_t>(is);
 	boost::uint32_t not_offset_table_offset = util::load<boost::uint32_t>(is);
 	if(is.fail() || offset_table_offset != ~not_offset_table_offset) {
 		is.clear();
+		debug("header offset checksum: " << print_hex(not_offset_table_offset) << " != ~"
+		                                 << print_hex(offset_table_offset));
 		return false;
 	}
 	
+	debug("found loader header at " << print_hex(offset_table_offset));
+	
 	return load_offsets_at(is, offset_table_offset);
 }
 
@@ -94,6 +102,10 @@ bool offsets::load_from_exe_resource(std::istream & is) {
 		return false;
 	}
 	
+	debug("found loader header resource at " << print_hex(resource.offset));
+	
+	found_magic = true;
+	
 	return load_offsets_at(is, resource.offset);
 }
 
@@ -101,12 +113,14 @@ bool offsets::load_offsets_at(std::istream & is, boost::uint32_t pos) {
 	
 	if(is.seekg(pos).fail()) {
 		is.clear();
+		debug("could not seek to loader header");
 		return false;
 	}
 	
 	char magic[12];
 	if(is.read(magic, std::streamsize(sizeof(magic))).fail()) {
 		is.clear();
+		debug("could not read loader header magic");
 		return false;
 	}
 	
@@ -115,6 +129,7 @@ bool offsets::load_offsets_at(std::istream & is, boost::uint32_t pos) {
 		BOOST_STATIC_ASSERT(sizeof(known_setup_loader_versions[i].magic) == sizeof(magic));
 		if(!memcmp(magic, known_setup_loader_versions[i].magic, sizeof(magic))) {
 			version = known_setup_loader_versions[i].version;
+			debug("found loader header magic version " << setup::version(version));
 			break;
 		}
 	}
@@ -131,6 +146,7 @@ bool offsets::load_offsets_at(std::istream & is, boost::uint32_t pos) {
 		boost::uint32_t revision = checksum.load<boost::uint32_t>(is);
 		if(is.fail()) {
 			is.clear();
+			debug("could not read loader header revision");
 			return false;
 		} else if(revision != 1) {
 			log_warning << "Unexpected setup loader revision: " << revision;
@@ -167,6 +183,7 @@ bool offsets::load_offsets_at(std::istream & is, boost::uint32_t pos) {
 	
 	if(is.fail()) {
 		is.clear();
+		debug("could not read loader header");
 		return false;
 	}
 	
@@ -174,6 +191,7 @@ bool offsets::load_offsets_at(std::istream & is, boost::uint32_t pos) {
 		boost::uint32_t expected = util::load<boost::uint32_t>(is);
 		if(is.fail()) {
 			is.clear();
+			debug("could not read loader header checksum");
 			return false;
 		}
 		if(checksum.finalize() != expected) {
@@ -186,6 +204,8 @@ bool offsets::load_offsets_at(std::istream & is, boost::uint32_t pos) {
 
 void offsets::load(std::istream & is) {
 	
+	found_magic = false;
+	
 	/*
 	 * Try to load the offset table by following a pointer at a constant offset.
 	 * This method of storing the offset table is used in versions before 5.1.5
diff --git a/src/loader/offsets.hpp b/src/loader/offsets.hpp
index 29bfe6c..f49bd91 100644
--- a/src/loader/offsets.hpp
+++ b/src/loader/offsets.hpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2011-2014 Daniel Scharrer
+ * Copyright (C) 2011-2020 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
@@ -50,6 +50,11 @@ namespace loader {
  */
 struct offsets {
 	
+	/*!
+	 * True if we have some indication that this is an Inno Setup file
+	 */
+	bool found_magic;
+	
 	/*!
 	 * Offset of compressed `setup.e32` (the actual installer code)
 	 *
diff --git a/src/release.cpp.in b/src/release.cpp.in
index ea9d61d..8908253 100644
--- a/src/release.cpp.in
+++ b/src/release.cpp.in
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2011-2015 Daniel Scharrer
+ * Copyright (C) 2011-2020 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
@@ -38,12 +38,12 @@
 
 const char innoextract_name[] = "${VERSION_0_NAME}";
 
-const char innoextract_version[] = "${VERSION_0_NUMBER}${GIT_SUFFIX_7}";
+const char innoextract_version[] = "${VERSION_0_STRING}${VERSION_SUFFIX}${GIT_SUFFIX_7}";
 
 const char innosetup_versions[] = "${VERSION_2}";
 
 const char innoextract_bugs[] = "${VERSION_4}";
 
-const char innoextract_copyright[] = "${LICENSE_0_STRING}";
+const char innoextract_copyright[] = "${LICENSE_0_LINE}";
 
 const char innoextract_license[] = "${LICENSE_TAIL}";
diff --git a/src/setup/delete.cpp b/src/setup/delete.cpp
index 628c038..dd7d20e 100644
--- a/src/setup/delete.cpp
+++ b/src/setup/delete.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2011-2019 Daniel Scharrer
+ * Copyright (C) 2011-2020 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
@@ -43,7 +43,7 @@ void delete_entry::load(std::istream & is, const info & i) {
 		(void)util::load<boost::uint32_t>(is); // uncompressed size of the entry
 	}
 	
-	is >> util::encoded_string(name, i.codepage);
+	is >> util::encoded_string(name, i.codepage, i.header.lead_bytes);
 	
 	load_condition_data(is, i);
 	
diff --git a/src/setup/directory.cpp b/src/setup/directory.cpp
index f2b6eee..0b0d15e 100644
--- a/src/setup/directory.cpp
+++ b/src/setup/directory.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2011-2019 Daniel Scharrer
+ * Copyright (C) 2011-2020 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
@@ -52,7 +52,7 @@ void directory_entry::load(std::istream & is, const info & i) {
 		(void)util::load<boost::uint32_t>(is); // uncompressed size of the entry
 	}
 	
-	is >> util::encoded_string(name, i.codepage);
+	is >> util::encoded_string(name, i.codepage, i.header.lead_bytes);
 	
 	load_condition_data(is, i);
 	
diff --git a/src/setup/file.cpp b/src/setup/file.cpp
index bb52123..bf7b3b4 100644
--- a/src/setup/file.cpp
+++ b/src/setup/file.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2011-2019 Daniel Scharrer
+ * Copyright (C) 2011-2020 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
@@ -81,11 +81,11 @@ void file_entry::load(std::istream & is, const info & i) {
 		(void)util::load<boost::uint32_t>(is); // uncompressed size of the entry
 	}
 	
-	is >> util::encoded_string(source, i.codepage);
-	is >> util::encoded_string(destination, i.codepage);
-	is >> util::encoded_string(install_font_name, i.codepage);
+	is >> util::encoded_string(source, i.codepage, i.header.lead_bytes);
+	is >> util::encoded_string(destination, i.codepage, i.header.lead_bytes);
+	is >> util::encoded_string(install_font_name, i.codepage, i.header.lead_bytes);
 	if(i.version >= INNO_VERSION(5, 2, 5)) {
-		is >> util::encoded_string(strong_assembly_name, i.codepage);
+		is >> util::encoded_string(strong_assembly_name, i.codepage, i.header.lead_bytes);
 	} else {
 		strong_assembly_name.clear();
 	}
diff --git a/src/setup/filename.cpp b/src/setup/filename.cpp
index e634055..5d6b37e 100644
--- a/src/setup/filename.cpp
+++ b/src/setup/filename.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2012-2019 Daniel Scharrer
+ * Copyright (C) 2012-2020 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
@@ -37,7 +37,7 @@ struct is_path_separator {
 
 struct is_unsafe_path_char {
 	bool operator()(char c) {
-		if(c < 32) {
+		if(static_cast<unsigned char>(c) < 32) {
 			return true;
 		}
 		switch(c) {
@@ -81,7 +81,8 @@ std::string filename_map::expand_variables(it & begin, it end, bool close) const
 		}
 		ptrdiff_t obegin = ptrdiff_t(result.size());
 		result.append(begin, pos);
-		result.erase(std::remove_if(result.begin() + obegin, result.end(), is_unsafe_path_char()), result.end());
+		std::replace_copy_if(result.begin() + obegin, result.end(), result.begin() + obegin,
+		                     is_unsafe_path_char(), '$');
 		begin = pos;
 		
 		if(pos == end) {
diff --git a/src/setup/header.cpp b/src/setup/header.cpp
index cacd5a0..614423d 100644
--- a/src/setup/header.cpp
+++ b/src/setup/header.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2011-2019 Daniel Scharrer
+ * Copyright (C) 2011-2020 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
@@ -493,10 +493,10 @@ void header::load(std::istream & is, const version & version) {
 		uninstall_display_size = 0;
 	}
 	
-	if(version == INNO_VERSION_EXT(5, 5, 0, 1)) {
+	if(version == INNO_VERSION_EXT(5, 4,  2, 1) || version == INNO_VERSION_EXT(5, 5, 0, 1)) {
 		/*
 		 * This is needed to extract an Inno Setup variant (BlackBox v2?) that uses
-		 * the 5.5.0 (unicode) data version string while the format differs:
+		 * the 5.4.2 or 5.5.0 (unicode) data version string while the format differs:
 		 * The language entries are off by one byte and the EncryptionUsed flag
 		 * gets set while there is no decrypt_dll.
 		 * I'm not sure where exactly this byte goes, but it's after the compression
@@ -715,24 +715,24 @@ void header::decode(util::codepage_id codepage) {
 	util::to_utf8(app_support_url, codepage);
 	util::to_utf8(app_updates_url, codepage);
 	util::to_utf8(app_version, codepage);
-	util::to_utf8(default_dir_name, codepage);
+	util::to_utf8(default_dir_name, codepage, &lead_bytes);
 	util::to_utf8(default_group_name, codepage);
-	util::to_utf8(base_filename, codepage);
-	util::to_utf8(uninstall_files_dir, codepage);
-	util::to_utf8(uninstall_name, codepage);
-	util::to_utf8(uninstall_icon, codepage);
-	util::to_utf8(app_mutex, codepage);
+	util::to_utf8(base_filename, codepage, &lead_bytes);
+	util::to_utf8(uninstall_files_dir, codepage, &lead_bytes);
+	util::to_utf8(uninstall_name, codepage, &lead_bytes);
+	util::to_utf8(uninstall_icon, codepage, &lead_bytes);
+	util::to_utf8(app_mutex, codepage, &lead_bytes);
 	util::to_utf8(default_user_name, codepage);
 	util::to_utf8(default_user_organisation, codepage);
 	util::to_utf8(default_serial, codepage);
-	util::to_utf8(app_readme_file, codepage);
+	util::to_utf8(app_readme_file, codepage, &lead_bytes);
 	util::to_utf8(app_contact, codepage);
 	util::to_utf8(app_comments, codepage);
-	util::to_utf8(app_modify_path, codepage);
-	util::to_utf8(create_uninstall_registry_key, codepage);
+	util::to_utf8(app_modify_path, codepage, &lead_bytes);
+	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(setup_mutex, codepage);
+	util::to_utf8(setup_mutex, codepage, &lead_bytes);
 	util::to_utf8(changes_environment, codepage);
 	util::to_utf8(changes_associations, codepage);
 	
diff --git a/src/setup/icon.cpp b/src/setup/icon.cpp
index cbfc99d..46be687 100644
--- a/src/setup/icon.cpp
+++ b/src/setup/icon.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2011-2019 Daniel Scharrer
+ * Copyright (C) 2011-2020 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
@@ -43,11 +43,11 @@ void icon_entry::load(std::istream & is, const info & i) {
 		(void)util::load<boost::uint32_t>(is); // uncompressed size of the entry
 	}
 	
-	is >> util::encoded_string(name, i.codepage);
-	is >> util::encoded_string(filename, i.codepage);
-	is >> util::encoded_string(parameters, i.codepage);
-	is >> util::encoded_string(working_dir, i.codepage);
-	is >> util::encoded_string(icon_file, i.codepage);
+	is >> util::encoded_string(name, i.codepage, i.header.lead_bytes);
+	is >> util::encoded_string(filename, i.codepage, i.header.lead_bytes);
+	is >> util::encoded_string(parameters, i.codepage, i.header.lead_bytes);
+	is >> util::encoded_string(working_dir, i.codepage, i.header.lead_bytes);
+	is >> util::encoded_string(icon_file, i.codepage, i.header.lead_bytes);
 	is >> util::encoded_string(comment, i.codepage);
 	
 	load_condition_data(is, i);
@@ -58,6 +58,14 @@ void icon_entry::load(std::istream & is, const info & i) {
 		app_user_model_id.clear();
 	}
 	
+	if(i.version >= INNO_VERSION(6, 1, 0)) {
+		const size_t guid_size = 16;
+		app_user_model_toast_activator_clsid.resize(guid_size);
+		is.read(&app_user_model_toast_activator_clsid[0], std::streamsize(guid_size));
+	} else {
+		app_user_model_toast_activator_clsid.clear();
+	}
+	
 	load_version_data(is, i.version);
 	
 	icon_index = util::load<boost::int32_t>(is, i.version.bits());
@@ -98,6 +106,9 @@ void icon_entry::load(std::istream & is, const info & i) {
 	if(i.version >= INNO_VERSION(5, 5, 0)) {
 		flagreader.add(PreventPinning);
 	}
+	if(i.version >= INNO_VERSION(6, 1, 0)) {
+		flagreader.add(HasAppUserModelToastActivatorCLSID);
+	}
 	
 	options = flagreader;
 }
diff --git a/src/setup/icon.hpp b/src/setup/icon.hpp
index 74f9fd9..97534d2 100644
--- a/src/setup/icon.hpp
+++ b/src/setup/icon.hpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2011-2019 Daniel Scharrer
+ * Copyright (C) 2011-2020 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
@@ -48,6 +48,7 @@ struct icon_entry : public item {
 		FolderShortcut,
 		ExcludeFromShowInNewInstall,
 		PreventPinning,
+		HasAppUserModelToastActivatorCLSID,
 		// obsolete options:
 		RunMinimized
 	);
@@ -65,6 +66,7 @@ struct icon_entry : public item {
 	std::string icon_file;
 	std::string comment;
 	std::string app_user_model_id;
+	std::string app_user_model_toast_activator_clsid;
 	
 	int icon_index;
 	
diff --git a/src/setup/info.cpp b/src/setup/info.cpp
index 0f7b831..9d4aa6e 100644
--- a/src/setup/info.cpp
+++ b/src/setup/info.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2011-2019 Daniel Scharrer
+ * Copyright (C) 2011-2020 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
@@ -150,10 +150,13 @@ void info::try_load(std::istream & is, entry_types entries, util::codepage_id fo
 	
 	stream::block_reader::pointer reader = stream::block_reader::get(is, version);
 	
+	debug("loading main header");
 	header.load(*reader, version);
 	
+	debug("loading languages");
 	load_entries(*reader, entries, header.language_count, languages, Languages);
 	
+	debug("determining encoding");
 	if(version.is_unicode()) {
 		// Unicode installers are always UTF16-LE, do not allow users to override that.
 		codepage = util::cp_utf16le;
@@ -180,27 +183,43 @@ void info::try_load(std::istream & is, entry_types entries, util::codepage_id fo
 	}
 	
 	if(version < INNO_VERSION(4, 0, 0)) {
+		debug("loading images and plugins");
 		load_wizard_and_decompressor(*reader, version, header, *this, entries);
 	}
 	
+	debug("loading messages");
 	load_entries(*reader, entries, header.message_count, messages, Messages);
+	debug("loading permissions");
 	load_entries(*reader, entries, header.permission_count, permissions, Permissions);
+	debug("loading types");
 	load_entries(*reader, entries, header.type_count, types, Types);
+	debug("loading components");
 	load_entries(*reader, entries, header.component_count, components, Components);
+	debug("loading tasks");
 	load_entries(*reader, entries, header.task_count, tasks, Tasks);
+	debug("loading directories");
 	load_entries(*reader, entries, header.directory_count, directories, Directories);
+	debug("loading files");
 	load_entries(*reader, entries, header.file_count, files, Files);
+	debug("loading icons");
 	load_entries(*reader, entries, header.icon_count, icons, Icons);
+	debug("loading ini entries");
 	load_entries(*reader, entries, header.ini_entry_count, ini_entries, IniEntries);
+	debug("loading registry entries");
 	load_entries(*reader, entries, header.registry_entry_count, registry_entries, RegistryEntries);
+	debug("loading delete entries");
 	load_entries(*reader, entries, header.delete_entry_count, delete_entries, DeleteEntries);
+	debug("loading uninstall delete entries");
 	load_entries(*reader, entries, header.uninstall_delete_entry_count, uninstall_delete_entries,
 	             UninstallDeleteEntries);
+	debug("loading run entries");
 	load_entries(*reader, entries, header.run_entry_count, run_entries, RunEntries);
+	debug("loading uninstall run entries");
 	load_entries(*reader, entries, header.uninstall_run_entry_count, uninstall_run_entries,
 	             UninstallRunEntries);
 	
 	if(version >= INNO_VERSION(4, 0, 0)) {
+		debug("loading images and plugins");
 		load_wizard_and_decompressor(*reader, version, header, *this, entries);
 	}
 	
@@ -208,6 +227,7 @@ void info::try_load(std::istream & is, entry_types entries, util::codepage_id fo
 	check_is_end(reader, "unknown data at end of primary header stream");
 	reader = stream::block_reader::get(is, version);
 	
+	debug("loading data entries");
 	load_entries(*reader, entries, header.data_entry_count, data_entries, DataEntries);
 	
 	check_is_end(reader, "unknown data at end of secondary header stream");
diff --git a/src/setup/ini.cpp b/src/setup/ini.cpp
index 836b483..9c70aef 100644
--- a/src/setup/ini.cpp
+++ b/src/setup/ini.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2011-2019 Daniel Scharrer
+ * Copyright (C) 2011-2020 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
@@ -47,13 +47,13 @@ void ini_entry::load(std::istream & is, const info & i) {
 		(void)util::load<boost::uint32_t>(is); // uncompressed size of the entry
 	}
 	
-	is >> util::encoded_string(inifile, i.codepage);
+	is >> util::encoded_string(inifile, i.codepage, i.header.lead_bytes);
 	if(inifile.empty()) {
 		inifile = "{windows}/WIN.INI";
 	}
-	is >> util::encoded_string(section, i.codepage);
+	is >> util::encoded_string(section, i.codepage, i.header.lead_bytes);
 	is >> util::encoded_string(key, i.codepage);
-	is >> util::encoded_string(value, i.codepage);
+	is >> util::encoded_string(value, i.codepage, i.header.lead_bytes);
 	
 	load_condition_data(is, i);
 	
diff --git a/src/setup/registry.cpp b/src/setup/registry.cpp
index 014b721..a86d4e4 100644
--- a/src/setup/registry.cpp
+++ b/src/setup/registry.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2011-2019 Daniel Scharrer
+ * Copyright (C) 2011-2020 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
@@ -65,7 +65,7 @@ void registry_entry::load(std::istream & is, const info & i) {
 		(void)util::load<boost::uint32_t>(is); // uncompressed size of the entry
 	}
 	
-	is >> util::encoded_string(key, i.codepage);
+	is >> util::encoded_string(key, i.codepage, i.header.lead_bytes);
 	if(i.version.bits() != 16) {
 		is >> util::encoded_string(name, i.codepage);
 	} else {
diff --git a/src/setup/run.cpp b/src/setup/run.cpp
index b29f9b8..0a83790 100644
--- a/src/setup/run.cpp
+++ b/src/setup/run.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2011-2019 Daniel Scharrer
+ * Copyright (C) 2011-2020 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
@@ -45,9 +45,9 @@ void run_entry::load(std::istream & is, const info & i) {
 		(void)util::load<boost::uint32_t>(is); // uncompressed size of the entry
 	}
 	
-	is >> util::encoded_string(name, i.codepage);
-	is >> util::encoded_string(parameters, i.codepage);
-	is >> util::encoded_string(working_dir, i.codepage);
+	is >> util::encoded_string(name, i.codepage, i.header.lead_bytes);
+	is >> util::encoded_string(parameters, i.codepage, i.header.lead_bytes);
+	is >> util::encoded_string(working_dir, i.codepage, i.header.lead_bytes);
 	if(i.version >= INNO_VERSION(1, 3, 9)) {
 		is >> util::encoded_string(run_once_id, i.codepage);
 	} else {
@@ -103,6 +103,9 @@ void run_entry::load(std::istream & is, const info & i) {
 	if(i.version >= INNO_VERSION(5, 2, 0)) {
 		flagreader.add(RunAsOriginalUser);
 	}
+	if(i.version >= INNO_VERSION(6, 1, 0)) {
+		flagreader.add(DontLogParameters);
+	}
 	
 	options = flagreader;
 }
diff --git a/src/setup/run.hpp b/src/setup/run.hpp
index af3032f..be832aa 100644
--- a/src/setup/run.hpp
+++ b/src/setup/run.hpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2011-2019 Daniel Scharrer
+ * Copyright (C) 2011-2020 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
@@ -49,7 +49,8 @@ struct run_entry : public item {
 		HideWizard,
 		Bits32,
 		Bits64,
-		RunAsOriginalUser
+		RunAsOriginalUser,
+		DontLogParameters
 	);
 	
 	enum wait_condition {
diff --git a/src/setup/version.cpp b/src/setup/version.cpp
index 0a1f064..5f1f2fe 100644
--- a/src/setup/version.cpp
+++ b/src/setup/version.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2011-2019 Daniel Scharrer
+ * Copyright (C) 2011-2020 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
@@ -160,11 +160,13 @@ const known_version versions[] = {
 	{ "Inno Setup Setup Data (5.3.10)",                     INNO_VERSION_EXT(5, 3, 10, 0), 0 },
 	{ "Inno Setup Setup Data (5.3.10) (u)",                 INNO_VERSION_EXT(5, 3, 10, 0), version::Unicode },
 	{ "Inno Setup Setup Data (5.4.2)",                      INNO_VERSION_EXT(5, 4,  2, 0), 0 },
-	{ "Inno Setup Setup Data (5.4.2) (u)",                  INNO_VERSION_EXT(5, 4,  2, 0), version::Unicode },
+	{ "Inno Setup Setup Data (5.4.2) (u)",  /* ambiguous */ INNO_VERSION_EXT(5, 4,  2, 0), version::Unicode },
+	{ "" /* BlackBox v1? */,                                INNO_VERSION_EXT(5, 4,  2, 1), 0 },
+	{ "" /* BlackBox v1? */,                                INNO_VERSION_EXT(5, 4,  2, 1), version::Unicode },
 	{ "Inno Setup Setup Data (5.5.0)",                      INNO_VERSION_EXT(5, 5,  0, 0), 0 },
 	{ "Inno Setup Setup Data (5.5.0) (u)",  /* ambiguous */ INNO_VERSION_EXT(5, 5,  0, 0), version::Unicode },
-	{ "" /* BlackBox v2? */,                                INNO_VERSION_EXT(5, 5,  0, 1), 0 },
-	{ "" /* BlackBox v2? */,                                INNO_VERSION_EXT(5, 5,  0, 1), version::Unicode },
+	{ "" /* BlackBox v2 / Inno Setup Ultra */,              INNO_VERSION_EXT(5, 5,  0, 1), 0 },
+	{ "" /* BlackBox v2 / Inno Setup Ultra */,              INNO_VERSION_EXT(5, 5,  0, 1), version::Unicode },
 	{ "Inno Setup Setup Data (5.5.6)",                      INNO_VERSION_EXT(5, 5,  6, 0), 0 },
 	{ "Inno Setup Setup Data (5.5.6) (u)",                  INNO_VERSION_EXT(5, 5,  6, 0), version::Unicode },
 	{ "Inno Setup Setup Data (5.5.7)",      /* ambiguous */ INNO_VERSION_EXT(5, 5,  7, 0), 0 },
@@ -178,6 +180,7 @@ const known_version versions[] = {
 	{ "Inno Setup Setup Data (5.6.2)",     /* prerelease */ INNO_VERSION_EXT(5, 6,  2, 0), 0 },
 	{ "Inno Setup Setup Data (5.6.2) (u)", /* prerelease */ INNO_VERSION_EXT(5, 6,  2, 0), version::Unicode },
 	{ "Inno Setup Setup Data (6.0.0) (u)",                  INNO_VERSION_EXT(6, 0,  0, 0), version::Unicode },
+	{ "Inno Setup Setup Data (6.1.0) (u)",                  INNO_VERSION_EXT(6, 1,  0, 0), version::Unicode },
 };
 
 } // anonymous namespace
@@ -277,7 +280,7 @@ void version::load(std::istream & is) {
 	char * end = std::find(version_string, version_string + boost::size(version_string), '\0');
 	std::string version_str(version_string, end);
 	debug("unknown version: \"" << version_str << '"');
-	if(version_str.find("Inno Setup") == std::string::npos) {
+	if(!boost::contains(version_str, "Inno Setup")) {
 		throw version_error();
 	}
 	
@@ -372,6 +375,11 @@ bool version::is_ambiguous() const {
 		return true;
 	}
 	
+	if(value == INNO_VERSION(5, 4, 2)) {
+		// might be either 5.4.2 or 5.4.2.1
+		return true;
+	}
+	
 	if(value == INNO_VERSION(5, 5, 0)) {
 		// might be either 5.5.0 or 5.5.0.1
 		return true;
diff --git a/src/util/boostfs_compat.hpp b/src/util/boostfs_compat.hpp
index ed1ab8f..0e8eec9 100644
--- a/src/util/boostfs_compat.hpp
+++ b/src/util/boostfs_compat.hpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2012-2018 Daniel Scharrer
+ * Copyright (C) 2012-2020 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
@@ -31,30 +31,6 @@
 #include <boost/filesystem/path.hpp>
 #include <boost/filesystem/operations.hpp>
 
-#if !defined(BOOST_FILESYSTEM_VERSION) || BOOST_FILESYSTEM_VERSION < 3
-
-namespace boost { namespace filesystem {
-
-inline bool create_directories(const path & p) {
-	
-	if(p.empty()) {
-		return true;
-	}
-	
-	path parent = p.parent_path();
-	if(!exists(parent)) {
-		if(!create_directories(parent)) {
-			return false;
-		}
-	}
-	
-	return create_directory(p);
-}
-
-} } // namespace boost::filesystem
-
-#endif
-
 namespace util {
 
 inline const std::string & as_string(const std::string & path) {
diff --git a/src/util/encoding.cpp b/src/util/encoding.cpp
index 20ec5fc..38b8b35 100644
--- a/src/util/encoding.cpp
+++ b/src/util/encoding.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2011-2019 Daniel Scharrer
+ * Copyright (C) 2011-2020 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
@@ -468,14 +468,41 @@ void utf16le_to_wtf8(const std::string & from, std::string & to) {
 	
 }
 
-void wtf8_to_utf16le(const std::string & from, std::string & to) {
+const char * wtf8_find_end(const char * begin, const char * end) {
+	
+	const char * i = end;
+	while(i != begin && is_utf8_continuation_byte(boost::uint8_t(*(i - 1)))) {
+		i--;
+	}
+	
+	if(i != begin) {
+		unicode_char chr = boost::uint8_t(*(i - 1));
+		size_t expected = 0;
+		if(chr & (1 << 7)) {
+			expected++;
+			if(chr & (1 << (5 + 6))) {
+				expected++;
+				if(chr & (1 << (4 + 6 + 6))) {
+					expected++;
+				}
+			}
+		}
+		if(expected > size_t(end - i)) {
+			return i - 1;
+		}
+	}
+	
+	return end;
+}
+
+void wtf8_to_utf16le(const char * begin, const char * end, std::string & to) {
 	
 	to.clear();
-	to.reserve(from.size() * 2); // optimistically, most strings only have ASCII characters
+	to.reserve(size_t(end - begin) * 2); // optimistically, most strings only have ASCII characters
 	
-	for(std::string::const_iterator i = from.begin(); i != from.end(); ) {
+	for(const char * i = begin; i != end; ) {
 		
-		unicode_char chr = utf8_read(i, from.end());
+		unicode_char chr = utf8_read(i, end);
 		
 		if(chr >= 0x10000) {
 			chr -= 0x10000;
@@ -491,6 +518,10 @@ void wtf8_to_utf16le(const std::string & from, std::string & to) {
 	
 }
 
+void wtf8_to_utf16le(const std::string & from, std::string & to) {
+	return wtf8_to_utf16le(from.c_str(), from.c_str() + from.size(), to);
+}
+
 namespace {
 
 unicode_char windows1252_replacements[] = {
@@ -752,7 +783,8 @@ bool from_utf8_win32(const std::string & from, std::string & to, codepage_id cod
 
 #endif // INNOEXTRACT_HAVE_WIN32_CONV
 
-void to_utf8(const std::string & from, std::string & to, codepage_id codepage) {
+void to_utf8(const std::string & from, std::string & to, codepage_id codepage,
+             const std::bitset<256> * lead_bytes) {
 	
 	switch(codepage) {
 		case cp_utf16le:     utf16le_to_wtf8(from, to); return;
@@ -761,6 +793,30 @@ void to_utf8(const std::string & from, std::string & to, codepage_id codepage) {
 		default: break;
 	}
 	
+	if(lead_bytes) {
+		std::string buffer;
+		for(size_t start = 0; start < from.length();) {
+			size_t end = start;
+			while(end < from.length()) {
+				if(lead_bytes->test(static_cast<unsigned char>(from[end]))) {
+					end = std::min(from.length(), end + 2);
+				} else if(from[end] != 0x5C) {
+					end++;
+				} else {
+					break;
+				}
+			}
+			buffer = from.substr(start, end - start);
+			util::to_utf8(buffer, codepage, NULL);
+			to.append(buffer);
+			if(end < from.length()) {
+				to.push_back('\\');
+			}
+			start = end + 1;
+		}
+		return;
+	}
+	
 	#if INNOEXTRACT_HAVE_ICONV
 	if(to_utf8_iconv(from, to, codepage)) {
 		return;
@@ -779,7 +835,7 @@ void to_utf8(const std::string & from, std::string & to, codepage_id codepage) {
 
 } // anonymous namespace
 
-void to_utf8(std::string & data, codepage_id codepage) {
+void to_utf8(std::string & data, codepage_id codepage, const std::bitset<256> * lead_bytes) {
 	
 	if(data.empty() || is_utf8(data, codepage)) {
 		// Already UTF-8
@@ -787,7 +843,7 @@ void to_utf8(std::string & data, codepage_id codepage) {
 	}
 	
 	std::string buffer;
-	to_utf8(data, buffer, codepage);
+	to_utf8(data, buffer, codepage, lead_bytes);
 	std::swap(data, buffer);
 }
 
diff --git a/src/util/encoding.hpp b/src/util/encoding.hpp
index 5de634c..e88d762 100644
--- a/src/util/encoding.hpp
+++ b/src/util/encoding.hpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2011-2019 Daniel Scharrer
+ * Copyright (C) 2011-2020 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
@@ -26,6 +26,7 @@
 #ifndef INNOEXTRACT_UTIL_ENCODING_HPP
 #define INNOEXTRACT_UTIL_ENCODING_HPP
 
+#include <bitset>
 #include <string>
 
 #include <boost/cstdint.hpp>
@@ -150,6 +151,16 @@ typedef boost::uint32_t codepage_id;
  */
 void utf16le_to_wtf8(const std::string & from, std::string & to);
 
+/*!
+ * Find the end of the last complete WTF-8 character in a string.
+ */
+const char * wtf8_find_end(const char * begin, const char * end);
+
+/*!
+ * Convert WTF-8 to UTF-16 while preserving unpaired surrogates.
+ */
+void wtf8_to_utf16le(const char * begin, const char * end, std::string & to);
+
 /*!
  * Convert WTF-8 to UTF-16 while preserving unpaired surrogates.
  */
@@ -157,12 +168,14 @@ void wtf8_to_utf16le(const std::string & from, std::string & to);
 
 /*!
  * Convert a string in place to UTF-8 from a specified encoding.
- * \param data     The input string to convert.
- * \param codepage The Windows codepage number for the input string encoding.
+ * \param data       The input string to convert.
+ * \param codepage   The Windows codepage number for the input string encoding.
+ * \param lead_bytes Preserve 0x5C path separators.
  *
  * \note This function is not thread-safe.
  */
-void to_utf8(std::string & data, codepage_id codepage = cp_windows1252);
+void to_utf8(std::string & data, codepage_id codepage = cp_windows1252,
+             const std::bitset<256> * lead_bytes = NULL);
 
 /*!
  * Convert a string from UTF-8 to a specified encoding.
diff --git a/src/util/load.cpp b/src/util/load.cpp
index 84c67a6..ac78cd3 100644
--- a/src/util/load.cpp
+++ b/src/util/load.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2011-2019 Daniel Scharrer
+ * Copyright (C) 2011-2020 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
@@ -54,9 +54,10 @@ void binary_string::skip(std::istream & is) {
 	discard(is, length);
 }
 
-void encoded_string::load(std::istream & is, std::string & target, codepage_id codepage) {
+void encoded_string::load(std::istream & is, std::string & target, codepage_id codepage,
+                          const std::bitset<256> * lead_bytes) {
 	binary_string::load(is, target);
-	to_utf8(target, codepage);
+	to_utf8(target, codepage, lead_bytes);
 }
 
 unsigned to_unsigned(const char * chars, size_t count) {
diff --git a/src/util/load.hpp b/src/util/load.hpp
index f3f3fac..703263d 100644
--- a/src/util/load.hpp
+++ b/src/util/load.hpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2011-2019 Daniel Scharrer
+ * Copyright (C) 2011-2020 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
@@ -26,6 +26,7 @@
 #ifndef INNOEXTRACT_UTIL_LOAD_HPP
 #define INNOEXTRACT_UTIL_LOAD_HPP
 
+#include <bitset>
 #include <cstring>
 #include <istream>
 #include <string>
@@ -89,34 +90,46 @@ struct encoded_string {
 	
 	std::string & data;
 	codepage_id codepage;
+	const std::bitset<256> * lead_byte_set;
 	
 	/*!
-	 * \param target  The std::string object to receive the loaded UTF-8 string.
-	 * \param cp      The Windows codepage for the encoding of the stored string.
+	 * \param target     The std::string object to receive the loaded UTF-8 string.
+	 * \param cp         The Windows codepage for the encoding of the stored string.
 	 */
-	encoded_string(std::string & target, codepage_id cp) : data(target), codepage(cp) { }
+	encoded_string(std::string & target, codepage_id cp)
+		: data(target), codepage(cp), lead_byte_set(NULL) { }
+	
+	/*!
+	 * \param target     The std::string object to receive the loaded UTF-8 string.
+	 * \param cp         The Windows codepage for the encoding of the stored string.
+	 * \param lead_bytes Preserve 0x5C path separators.
+	 */
+	encoded_string(std::string & target, codepage_id cp, const std::bitset<256> & lead_bytes)
+		: data(target), codepage(cp), lead_byte_set(&lead_bytes) { }
 	
 	/*!
 	 * Load and convert a length-prefixed string
 	 *
 	 * \note This function is not thread-safe.
 	 */
-	static void load(std::istream & is, std::string & target, codepage_id codepage);
+	static void load(std::istream & is, std::string & target, codepage_id codepage,
+	                 const std::bitset<256> * lead_bytes = NULL);
 	
 	/*!
 	 * Load and convert a length-prefixed string
 	 *
 	 * \note This function is not thread-safe.
 	 */
-	static std::string load(std::istream & is, codepage_id codepage) {
+	static std::string load(std::istream & is, codepage_id codepage,
+	                        const std::bitset<256> * lead_bytes = NULL) {
 		std::string target;
-		load(is, target, codepage);
+		load(is, target, codepage, lead_bytes);
 		return target;
 	}
 	
 };
 inline std::istream & operator>>(std::istream & is, const encoded_string & str) {
-	encoded_string::load(is, str.data, str.codepage);
+	encoded_string::load(is, str.data, str.codepage, str.lead_byte_set);
 	return is;
 }
 
diff --git a/src/util/windows.cpp b/src/util/windows.cpp
index 9a978d7..16ebf22 100644
--- a/src/util/windows.cpp
+++ b/src/util/windows.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013-2019 Daniel Scharrer
+ * Copyright (C) 2013-2020 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
@@ -75,9 +75,8 @@ class windows_console_sink : public util::ansi_console_parser<windows_console_si
 	const HANDLE handle;
 	
 	//! Buffer for charset conversions
-	std::vector<wchar_t> buffer;
-	const utf8_codecvt * codecvt;
-	utf8_codecvt::state_type codecvt_state;
+	std::string in_buffer;
+	std::string out_buffer;
 	
 	//! Initial console display attributes
 	WORD initial_attributes;
@@ -236,6 +235,10 @@ class windows_console_sink : public util::ansi_console_parser<windows_console_si
 	}
 	
 	void handle_command(CommandType type, const char * codes, const char * end) {
+		if(!in_buffer.empty()) {
+			output_text(in_buffer.c_str(), in_buffer.c_str() + in_buffer.size());
+			in_buffer.clear();
+		}
 		switch(type) {
 			case EL:  erase_in_line(codes, end); break;
 			case SGR: select_graphic_rendition(codes, end); break;
@@ -249,7 +252,7 @@ class windows_console_sink : public util::ansi_console_parser<windows_console_si
 		}
 	}
 	
-	void handle_deferred_clear(wchar_t * & begin, wchar_t * end) {
+	void handle_deferred_clear(LPCWSTR & begin, LPCWSTR end) {
 		
 		CONSOLE_SCREEN_BUFFER_INFO info;
 		if(!GetConsoleScreenBufferInfo(handle, &info)) {
@@ -265,8 +268,8 @@ class windows_console_sink : public util::ansi_console_parser<windows_console_si
 				break;
 			}
 			
-			wchar_t * cr = std::find(begin, end, L'\r');
-			wchar_t * ln = std::find(begin, cr, L'\n');
+			LPCWSTR cr = std::find(begin, end, L'\r');
+			LPCWSTR ln = std::find(begin, cr, L'\n');
 			
 			// Insert an empty line before the "cleared" line
 			if(clear_line == info.dwCursorPosition.Y) {
@@ -338,48 +341,48 @@ class windows_console_sink : public util::ansi_console_parser<windows_console_si
 		
 	}
 	
+	void output_text(const char * begin, const char * end) {
+		
+		wtf8_to_utf16le(begin, end, out_buffer);
+		
+		LPCWSTR obegin = reinterpret_cast<LPCWSTR>(out_buffer.c_str());
+		LPCWSTR oend = obegin + out_buffer.size() / 2;
+		
+		if(deferred_clear) {
+			handle_deferred_clear(obegin, oend);
+		}
+		
+		DWORD count;
+		WriteConsoleW(handle, obegin, DWORD(oend - obegin), &count, NULL);
+		
+	}
+	
 	void handle_text(const char * s, size_t n) {
 		
+		if(!in_buffer.empty()) {
+			in_buffer.append(s, n);
+			s = in_buffer.c_str();
+			n = in_buffer.size();
+		}
+		
 		const char * end = s + n;
 		
 		if(s == end) {
 			return;
 		}
 		
-		for(;;) {
-			
-			wchar_t * obegin = &buffer.front();
-			wchar_t * oend = obegin + buffer.size();
-			wchar_t * onext = obegin;
-			
-			std::codecvt_base::result res;
-			res = codecvt->in(codecvt_state, s, end, s, obegin, oend, onext);
-			
-			if(deferred_clear) {
-				handle_deferred_clear(obegin, onext);
-			}
-			
-			DWORD count;
-			WriteConsoleW(handle, obegin, DWORD(onext - obegin), &count, NULL);
-			
-			if(res != std::codecvt_base::partial) {
-				break;
-			}
-			
-			if(onext == oend) {
-				buffer.resize(buffer.size() * 2);
-			}
-			
-		}
+		const char * e = wtf8_find_end(s, end);
+		
+		output_text(s, e);
+		
+		in_buffer.assign(e, end);
 		
 	}
 	
 public:
 	
-	windows_console_sink(HANDLE console_handle, const utf8_codecvt * converter)
+	explicit windows_console_sink(HANDLE console_handle)
 		: handle(console_handle)
-		, buffer(256)
-		, codecvt(converter)
 		, initial_attributes(get_attributes())
 		, default_attributes(get_default_attributes())
 		, attributes(initial_attributes)
@@ -389,6 +392,9 @@ public:
 	{ }
 	
 	~windows_console_sink() {
+		if(!in_buffer.empty()) {
+			output_text(in_buffer.c_str(), in_buffer.c_str() + in_buffer.size());
+		}
 		if(deferred_clear) {
 			CONSOLE_SCREEN_BUFFER_INFO info;
 			if(GetConsoleScreenBufferInfo(handle, &info)) {
@@ -400,9 +406,6 @@ public:
 	
 };
 
-// UTF8 -> UTF-16 converter
-static utf8_codecvt codecvt;
-
 typedef boost::iostreams::stream_buffer<windows_console_sink> console_buffer;
 struct console_buffer_info {
 	HANDLE handle;
@@ -440,7 +443,7 @@ static bool is_console(HANDLE handle) {
 static void init_console(std::ostream & os, console_buffer_info & info, DWORD n) {
 	info.handle = GetStdHandle(n);
 	if(is_console(info.handle)) {
-		info.buf = new console_buffer(info.handle, &codecvt);
+		info.buf = new console_buffer(info.handle);
 		info.orig_buf = os.rdbuf(info.buf);
 	} else {
 		info.handle = NULL;
@@ -521,7 +524,7 @@ int main() {
 	}
 	
 	// Tell boost::filesystem to interpret our path strings as UTF-8
-	boost::filesystem::path::imbue(std::locale(std::locale(), &util::codecvt));
+	boost::filesystem::path::imbue(std::locale(std::locale(), new utf8_codecvt()));
 	
 	// Enable UTF-8 output and ANSI escape sequences
 	util::console_wrapper wrapped;

More details

Full run details